1??runtime介紹:
runtime是一套比較底層的純C語言API, 包含了很多底層的C語言API史煎。在我們平時(shí)編寫的OC代碼中, 程序運(yùn)行過程時(shí), 其實(shí)最終都是轉(zhuǎn)成了runtime的C語言代碼.
比如說衩藤,下面一個(gè)創(chuàng)建對(duì)象的方法 :
1.[[ZSPerson alloc] init]
2.runtime :objc_msgSend(objc_msgSend(“ZSPerson” , “alloc”), “init”)
2??runtime 用來干什么呢吧慢??用在那些地方呢慷彤?怎么用呢娄蔼?
runtime是屬于OC的底層, 可以進(jìn)行一些非常底層的操作(用OC是無法現(xiàn)實(shí)的, 不好實(shí)現(xiàn))
在程序運(yùn)行過程中, 動(dòng)態(tài)創(chuàng)建一個(gè)類(比如KVO的底層實(shí)現(xiàn))
在程序運(yùn)行過程中, 動(dòng)態(tài)地為某個(gè)類添加屬性\方法, 修改屬性值\方法
遍歷一個(gè)類的所有成員變量(屬性)\所有方法
例如:我們需要對(duì)一個(gè)類的屬性進(jìn)行歸檔解檔的時(shí)候?qū)傩蕴貏e的多,這時(shí)候底哗,我們就會(huì)寫很多對(duì)應(yīng)的代碼岁诉,但是如果使用了runtime就可以動(dòng)態(tài)設(shè)置!
例如跋选,ZSPerson.h的文件如下所示
@interfaceZSPerson:NSObject
@property(nonatomic,assign)intage;
@property(nonatomic,assign)intheight;
@property(nonatomic,copy)NSString*name;
@property(nonatomic,assign)intage2;
@property(nonatomic,assign)intheight2;
@property(nonatomic,assign)intage3;
@property(nonatomic,assign)intheight3;
@property(nonatomic,assign)intage4;
@property(nonatomic,assign)intheight4;
@end
而ZSPerson.m實(shí)現(xiàn)文件的內(nèi)容如下
#import"ZSPerson.h"
@implementationZSPerson
(void)encodeWithCoder:(NSCoder )encoder?
{
unsignedintcount =0;
Ivar ivars = class_copyIvarList([ZSPerson class], &count);
for(int i =0; i<count;i++){
// 取出i位置對(duì)應(yīng)的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
constchar*name = ivar_getName(ivar);
// 歸檔
NSString*key = [NSStringstringWithUTF8String:name];
idvalue = [selfvalueForKey:key];?
? [encoder encodeObject:value forKey:key];?
? }
free(ivars);
? ? }?
? (id)initWithCoder:(NSCoder *)decoder?
? {
if(self= [super init]) {
unsignedintcount =0;
?Ivar *ivars = class_copyIvarList([ZSPerson class], &count);
for(int i =0; i<count;i++){
// 取出i位置對(duì)應(yīng)的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char*name = ivar_getName(ivar);
// 歸檔
NSString*key = [NSStringstringWithUTF8String:name];
id value = [decoder decodeObjectForKey:key];
// 設(shè)置到成員變量身上
[selfsetValue:value forKey:key];
free(ivars);
? }
returnself;?
? }
@end
這樣我們可以看到歸檔和解檔的案例其實(shí)是runtime寫下的
學(xué)習(xí)涕癣,runtime機(jī)制首先要了解下面幾個(gè)問題
1.相關(guān)的頭文件和函數(shù)
a> 頭文件
利用頭文件,我們可以查看到runtime中的各個(gè)方法!
b> 相關(guān)應(yīng)用
NSCoding(歸檔和解檔, 利用runtime遍歷模型對(duì)象的所有屬性)
字典 –> 模型 (利用runtime遍歷模型對(duì)象的所有屬性, 根據(jù)屬性名從字典中取出對(duì)應(yīng)的值, 設(shè)置到模型的屬性上)
KVO(利用runtime動(dòng)態(tài)產(chǎn)生一個(gè)類)
用于封裝框架(想怎么改就怎么改)
這就是我們r(jià)untime機(jī)制的只要運(yùn)用方向
c> 相關(guān)函數(shù)
objc_msgSend : 給對(duì)象發(fā)送消息
class_copyMethodList : 遍歷某個(gè)類所有的方法
class_copyIvarList : 遍歷某個(gè)類所有的成員變量
class_…..
這是我們學(xué)習(xí)runtime必須知道的函數(shù)坠韩!
2.必備常識(shí)
a> Ivar : 成員變量
b> Method : 成員方法
從上面例子中我們看到我們定義的成員變量距潘,如果要是動(dòng)態(tài)創(chuàng)建方法,可以使用Method只搁。
3??接下來我們進(jìn)行項(xiàng)目實(shí)戰(zhàn)
首先給UITableViewCell創(chuàng)建一個(gè)分類RightDownPlugin
.h文件中
#import<UIKit,UIKit.h>
@interfaceUITableViewCell(RightDownPlugin)
@property(nonatomic,strong)UIImageView*statusImgV;//狀態(tài)圖@property(nonatomic,strong)UILabel*statusLab;//狀態(tài)label
@end
.m文件
#import"UITableViewCell+RightDownPlugin.h"
#include <objc/runtime.h>
@implementationUITableViewCell(RightDownPlugin)
//定義常量 必須是C語言字符串 因?yàn)閞untime是C語言API音比,
staticchar*statusImgKey ="statusImgKey";
staticchar*statusLabKey ="statusLabKey";
/*?
OBJC_ASSOCIATION_ASSIGN;? ? ? ? ? ? //assign策略
OBJC_ASSOCIATION_COPY_NONATOMIC;? ? //copy策略
OBJC_ASSOCIATION_RETAIN_NONATOMIC;? // retain策略
OBJC_ASSOCIATION_RETAIN;
OBJC_ASSOCIATION_COPY;
*//*
id object 給哪個(gè)對(duì)象的屬性賦值
const void *key 屬性對(duì)應(yīng)的key
id value? 設(shè)置屬性值為value
objc_AssociationPolicy policy? 使用的策略,是一個(gè)枚舉值氢惋,和copy洞翩,retain,assign是一樣的焰望,手機(jī)開發(fā)一般都選擇nonatomic
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
*/// 然后就需要自定義set和get方法了骚亿,因?yàn)閏ategory默認(rèn)是不能添加屬性的,
- (void)setStatusImgV:(UIImageView*)statusImgV{? ? objc_setAssociatedObject(self,statusImgKey,statusImgV,OBJC_ASSOCIATION_RETAIN);
}
- (UIImageView*)statusImgV{
return objc_getAssociatedObject(self, statusImgKey);
}
// Lab
- (void)setStatusLab:(UILabel*)statusLab{? ? objc_setAssociatedObject(self,statusLabKey,statusLab,OBJC_ASSOCIATION_RETAIN);
}
- (UILabel*)statusLab{
return objc_getAssociatedObject(self, statusLabKey);
}
@end
runtime常見的用法總結(jié)
1.runtime動(dòng)態(tài)添加屬性
需求:給NSString類添加兩個(gè)屬性:name和count
NSString+String.h中
#import<Foundation/Foundation.h>
/**
*? @property在分類里添加一個(gè)屬性 不會(huì)有setter getter方法 只聲明了一個(gè)變量 而且 這樣聲明 這個(gè)屬性和這個(gè)類沒有什么關(guān)系 */
@interface NSString (String)
@property (copy, nonatomic, nullable) NSString *name;
@property (copy, nonatomic, nullable) NSString *count;
@end
NSString+String.m中
#import "NSString+String.h"
#import<objc/message>
/**
*? 動(dòng)態(tài)添加屬性的本質(zhì)是 指向外部已經(jīng)存在的一個(gè)屬性 而不是去在對(duì)象中創(chuàng)建一個(gè)屬性
*/
@implementation NSString (String)
static NSString *_name;
//在分類里聲明屬性 需要自己寫set方法和get方法
- (void) setName:(NSString *)name
{
_name = name;
}
- (NSString *) name
{
return _name;
}
//添加屬性 應(yīng)該是與對(duì)象有關(guān)
- (void) setCount:(NSString *)count
{
//set方法里設(shè)置關(guān)聯(lián)
//Associated 關(guān)聯(lián) 聯(lián)系
//跟某個(gè)對(duì)象產(chǎn)生關(guān)聯(lián),添加屬性
/**
* id obj 給哪個(gè)對(duì)象添加屬性(產(chǎn)生關(guān)聯(lián))
* const void *key 屬性名 (根據(jù)key獲取關(guān)聯(lián)的對(duì)象) void * 相當(dāng)于 id 萬能指針 傳c或者oc的都可以
* id value 要關(guān)聯(lián)的值
* objc_AssociationPolicy policy 策略 宏對(duì)應(yīng)assign retain copy (因?yàn)閣eak沒有用 外面賦值完馬上就會(huì)被銷毀 所以沒有weak)
*/
objc_setAssociatedObject(self, @"count", count, OBJC_ASSOCIATION_ASSIGN);
}
- (NSString *) count
{
//get方法里獲取關(guān)聯(lián)
return [NSString stringWithFormat:@"%ld",[objc_getAssociatedObject(self, @"count") length]];
}
@end
2.runtime動(dòng)態(tài)加載方法
ViewController中
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@end
@implementation ViewController
- (void) viewDidLoad{? ? [super viewDidLoad];?
? //performSelector:動(dòng)態(tài)添加方法? ?
Person *p0 = [[Person alloc] init];??
[p0 performSelector:@selector(eat)];
//動(dòng)態(tài)添加方法 但是如果沒有實(shí)現(xiàn)該方法 還是會(huì)崩潰?
[p0 performSelector:@selector(drink:) withObject:@"juice"];
//動(dòng)態(tài)添加方法 但是如果沒有實(shí)現(xiàn)該方法 還是會(huì)崩潰
}
Person.m中
#import "Person.h"
#import<objc/message.h>
//默認(rèn)一個(gè)方法都有兩個(gè)參數(shù):self 和_cmd? self是方法的調(diào)用者 _cmd就是調(diào)用方法的編號(hào)(方法名) 這兩個(gè)參數(shù)為隱式參數(shù) 但是如果調(diào)用的是c的函數(shù) 則需要寫出來
@implementation Person
//定義函數(shù) 該函數(shù)名是啥都可以
void eat(id self,SEL _cmd)
{
//無返回值 兩個(gè)參數(shù) void(id,SEL) v@:
NSLog(@"%@調(diào)用了%@方法",[self class],NSStringFromSelector(_cmd));//SEL本身沒發(fā)打印 只能打印方法名
}
void drink(id self,SEL _cmd,id param1)
{
//v void? ? @ 對(duì)象? ? : 方法編號(hào)
NSLog(@"%@調(diào)用了%@方法 傳遞參數(shù):%@",[self class],NSStringFromSelector(_cmd),param1);//SEL本身沒發(fā)打印 只能打印方法名
}
//1.動(dòng)態(tài)添加方法 首先要實(shí)現(xiàn)resolveInstanceMethod:方法或resolveClassMethod:方法
//前者對(duì)應(yīng)實(shí)例方法 后者對(duì)應(yīng)類方法
//這兩個(gè)方法的作用是要知道哪個(gè)方法沒有被實(shí)現(xiàn)
//這兩個(gè)方法是在當(dāng)該類的某個(gè)方法沒有實(shí)現(xiàn),但是又被外界調(diào)用了的時(shí)候調(diào)用 (及:外界試用performSelector:調(diào)用了該類中某個(gè)沒有實(shí)現(xiàn)的方法)
//sel參數(shù)為沒有被實(shí)現(xiàn)的這個(gè)方法
+ (BOOL) resolveInstanceMethod:(SEL)sel
{
//打印該方法名
//? ? NSLog(@"%@",NSStringFromSelector(sel));
//動(dòng)態(tài)添加方法
if ([NSStringFromSelector(sel) isEqualToString:@"eat"])//sel == @selector(eat)也可以 但是會(huì)報(bào)警
{
/**
*? Class 給哪個(gè)類添加方法
*? sel 要添加的方法編號(hào)(方法名)
*? IMP 方法的實(shí)現(xiàn) ———— 函數(shù)的入口(函數(shù)的指針 函數(shù)名 是啥都可以 不一定和sel相同)
*? types 方法的類型 編碼格式 (類型c語言的字符串) (函數(shù)的類型:返回值類型 參數(shù)類型 直接查文檔 文檔有表格)
*/
class_addMethod([self class], sel, (IMP)eat, "v@:");
//處理完了要返回YES
//? ? ? ? return YES;
}
else if ([NSStringFromSelector(sel) isEqualToString:@"drink:"])//要加冒號(hào)
{
class_addMethod([self class], sel, (IMP)drink, "v@:@");
//? ? ? ? return YES;
}
//由于不知道返回的YES還是NO 所以:
return [super resolveInstanceMethod:sel];
}
+ (BOOL) resolveClassMethod:(SEL)sel
{
return [super resolveClassMethod:sel];
}
@end
3.runtime交換方法(ios黑魔法)
ViewController.m中
#import "ViewController.h"
#import<objc/message.h>
#import "Person.h"
//#import "UIImage+image.h"http://交換方法時(shí)候不用導(dǎo)入也可以 因?yàn)榻粨Q寫在類+(void)load里
@interface ViewController ()
@end
@implementation ViewController
- (void) viewDidLoad{? ? [super viewDidLoad];?
? /**?
? *? 交換方法??
*///??
UIImage *image = [UIImage ov_imageNamed:@"123"];??
UIImage *image1 = [UIImage imageNamed:@"123"];??
UIImage *image2 = [UIImage imageNamed:@"123"];
? //用imageNamed加載圖片,并不知道圖片是否加載成功??
//需求:在以后調(diào)用imageNamed的時(shí)候,要知道圖片是否加載成功??
//交換方法的實(shí)現(xiàn) (把imageNamed:方法和ov_imageNamed:方法交換 及 調(diào)用imageNamed就是調(diào)用ov_imageNamed)
}
@end
UIImage+image.m中
#import"UIImage+image.h"
#import<objc/message.h>
@implementation UIImage (image)
//加載這個(gè)分類的時(shí)候調(diào)用
+ (void) load
{
NSLog(@"%s",__func__);
//方法都定義在類里面 所以 交換對(duì)象方法也用class_開頭
/**
*? class_getMethodImplementation 獲取類方法的實(shí)現(xiàn)
*
*? Class 獲取哪個(gè)類的方法
*? SEL 獲取哪個(gè)方法
*? class_getMethodImplementation(<#__unsafe_unretained Class cls#>, <#SEL name#>)
*/
/**
*? class_getInstanceMethod 獲取對(duì)象方法
*
*? Class 獲取哪個(gè)類的方法
*? SEL 獲取哪個(gè)方法
*
*? class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
*/
/**
*? class_getClassMethod 獲取類方法
*
*? class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)
*/
/**
*? method_exchangeImplementations交換方法
*
*? Method m1? 要被替換的方法
*? Method m2? 要替換Method m1的方法
*? method_exchangeImplementations(<#Method m1#>, <#Method m2#>)
*/
//交換方法的實(shí)現(xiàn)
//1.拿到兩個(gè)方法
Method imageNamedMethod = class_getClassMethod([self class], @selector(imageNamed:));
Method ov_imageNamedMethod = class_getClassMethod([self class], @selector(ov_imageNamed:));
//2.交換
method_exchangeImplementations(imageNamedMethod, ov_imageNamedMethod);
}
/**
*? 分類沒有父類 沒有super
*/
//+ (nullable __kindof UIImage *) imageNamed:(nonnull NSString *)imageName
//{
//? ? return nil;
//}
/**
*? 用其他方法做 這個(gè)方法不好的原因是 1.導(dǎo)入頭文件太蛋疼 2.團(tuán)隊(duì)其他人可能不知道
*/
+ (nullable __kindof UIImage *) ov_imageNamed:(nonnull NSString *)imageName
{
//1.加載圖片功能
//? ? UIImage *image = [UIImage imageNamed:imageName];//由于使用了方法交換 所以這里再調(diào)用該方法就會(huì)造成死循環(huán)
UIImage *image = [UIImage ov_imageNamed:imageName];//此處直接調(diào)用方法本身即可
NSLog(@"%s %d",__func__,__LINE__);
//2.判斷返回是否為空功能
if (!image)
{
//NSException 為拋異常(強(qiáng)制崩潰)
//? ? ? ? NSException *e = [NSException
//? ? ? ? ? ? ? ? ? ? ? ? ? exceptionWithName: @"異常情況"
//? ? ? ? ? ? ? ? ? ? ? ? ? reason: @"圖片為空"
//? ? ? ? ? ? ? ? ? ? ? ? ? userInfo: nil];
//? ? ? ? @throw e;
}
else
{
}
return image;
}
@end