一.什么是 runtime ?
rumtime是運(yùn)行時(shí)庫,基于c語言的api接口,
作用是動(dòng)態(tài)的創(chuàng)建一個(gè)類 動(dòng)態(tài)的添加屬性和方法 遍歷屬性和方法名 動(dòng)態(tài)修改屬性和方法等等
1.能動(dòng)態(tài)產(chǎn)生一個(gè)類,一個(gè)成員變量,一個(gè)方法
2.能動(dòng)態(tài)修改一個(gè)類,一個(gè)成員變量,一個(gè)方法
3.能動(dòng)態(tài)刪除一個(gè)類,一個(gè)成員變量,一個(gè)方法
//類在runtime中的表示
struct objc_class {
Class isa;//指針,顧名思義,表示是一個(gè)什么,
//實(shí)例的isa指向類對(duì)象,類對(duì)象的isa指向元類
#if !__OBJC2__
Class super_class; //指向父類
const char *name; //類名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成員變量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//緩存
//一種優(yōu)化,調(diào)用過的方法存入緩存列表,下次調(diào)用先找緩存
struct objc_protocol_list *protocols //協(xié)議列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
二.runtime的頭文件
#import <objc/runtime.h> 包含對(duì)類、成員變量、屬性、方法的操作
#import <objc> 包含消息機(jī)制
三.消息發(fā)送步驟
1)受限檢測這個(gè) selector 是不是要忽略。比如Mac OS X 開發(fā),有了垃圾回收就不理會(huì)retain , release這些函數(shù)。
2)檢測這個(gè) selector 的 target 是不是 nil , Objt 允許我們對(duì)一個(gè) nil 對(duì)象執(zhí)行任何方法不會(huì) Crash ,因?yàn)檫\(yùn)行時(shí)會(huì)被忽略掉。
3)如果上面兩步都通過了,那么就開始查找這個(gè)類的實(shí)現(xiàn) IMP , 先從 cache 哩查找,如果找到了就運(yùn)行對(duì)應(yīng)的函數(shù)去執(zhí)行相應(yīng)的代碼。
4)如果 cache 找不到就找類的方法列表中是否有對(duì)應(yīng)的方法。
5)如果類的方法列表找不到就到父類的方法列表中查找,一直找到 NSObjiect 類為止。
6)如果還找不到,進(jìn)入動(dòng)態(tài)方法解析。
四.常用方法
//動(dòng)態(tài)攔截調(diào)用
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//遍歷相關(guān)
class_copyMethodList(返回一個(gè)指向類的方法數(shù)組的指針)
class_copyIvarList (返回一個(gè)指向類的成員變量數(shù)組的指針)
class_copyPropertyList(返回一個(gè)指向類的屬性數(shù)組的指針)
//修改屬性
objc_getAssociatedObject
objc_setAssocaitedObject
//交換
class_getInstanceMethod
method_exchangeImplementation(systemMethod, swizzMethod)
五.應(yīng)用
1)動(dòng)態(tài)的遍歷一個(gè)類的所有成員變量,用于字典轉(zhuǎn)模型,歸檔解檔操作
- (void)viewDidLoad {
[super viewDidLoad];
/** 利用runtime遍歷一個(gè)類的全部成員變量
1.導(dǎo)入頭文件<objc/runtime.h> */
unsigned int count = 0;
/** Ivar:表示成員變量類型 */
Ivar *ivars = class_copyIvarList([BDPerson class], &count);//獲得一個(gè)指向該類成員變量的指針
for (int i =0; i < count; i ++) {
//獲得Ivar
Ivar ivar = ivars[i]; //根據(jù)ivar獲得其成員變量的名稱--->C語言的字符串
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
NSLog(@"%d----%@",i,key);
}
}
2)可以利用遍歷類的屬性,來快速的進(jìn)行歸檔操作;將從網(wǎng)絡(luò)上下載的json數(shù)據(jù)進(jìn)行字典轉(zhuǎn)模型。
注意:歸檔解檔需要遵守<NSCoding>協(xié)議,實(shí)現(xiàn)以下兩個(gè)方法
- (void)encodeWithCoder:(NSCoder *)encoder{
//歸檔存儲(chǔ)自定義對(duì)象
unsigned int count = 0;
//獲得指向該類所有屬性的指針
objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);
for (int i =0; i < count; i ++) {
//獲得
objc_property_t property = properties[i]; //根據(jù)objc_property_t獲得其屬性的名稱--->C語言的字符串
const char *name = property_getName(property);
NSString *key = [NSString stringWithUTF8String:name];
// 編碼每個(gè)屬性,利用kVC取出每個(gè)屬性對(duì)應(yīng)的數(shù)值
[encoder encodeObject:[self valueForKeyPath:key] forKey:key];
}}
- (instancetype)initWithCoder:(NSCoder *)decoder{
//歸檔存儲(chǔ)自定義對(duì)象
unsigned int count = 0;
//獲得指向該類所有屬性的指針
objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);
for (int i =0; i < count; i ++) {
objc_property_t property = properties[i]; //根據(jù)objc_property_t獲得其屬性的名稱--->C語言的字符串
const char *name = property_getName(property);
NSString *key = [NSString stringWithUTF8String:name]; //解碼每個(gè)屬性,利用kVC取出每個(gè)屬性對(duì)應(yīng)的數(shù)值
[self setValue:[decoder decodeObjectForKey:key] forKeyPath:key];
}
return self;
}
3)交換方法
一.例如數(shù)組越界問題,防止系統(tǒng)崩潰(NSMutableArray 添加空值會(huì)出現(xiàn)崩潰)
① 新建一個(gè)分類,分類中引入頭文件,實(shí)現(xiàn)下列方法
+ (void)load{
Method orginalMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:));
Method newMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(Mn_addObject:));
method_exchangeImplementations(orginalMethod, newMethod);
}
- (void)Mn_addObject:(id)object{
if (object) {
[self Mn_addObject:object];
}
}
②在項(xiàng)目文件中,正常使用,若添加空值,不會(huì)崩潰只會(huì)出現(xiàn)報(bào)警信息
NSMutableArray *arr = [NSMutableArray array];
[arr addObject:nil];
二.生命周期
①創(chuàng)建分類
//load方法會(huì)在類第一次加載的時(shí)候被調(diào)用
//調(diào)用的時(shí)間比較靠前,適合在這個(gè)方法里做方法交換
+ (void)load{
//方法交換應(yīng)該被保證,在程序中只會(huì)執(zhí)行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//獲得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己實(shí)現(xiàn)的將要被交換的方法的selector
SEL swizzSel = @selector(swiz_viewWillAppear:);
//兩個(gè)方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
//首先動(dòng)態(tài)添加方法,實(shí)現(xiàn)是被交換的方法,返回值表示添加成功還是失敗
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//如果成功,說明類中不存在這個(gè)方法的實(shí)現(xiàn)
//將被交換方法的實(shí)現(xiàn)替換到這個(gè)并不存在的實(shí)現(xiàn)
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否則,交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(systemMethod, swizzMethod);
}
});
}
- (void)swiz_viewWillAppear:(BOOL)animated{
//這時(shí)候調(diào)用自己,看起來像是死循環(huán)
//但是其實(shí)自己的實(shí)現(xiàn)已經(jīng)被替換了
[self swiz_viewWillAppear:animated];
NSLog(@"swizzle");
}
②在一個(gè)自己定義的viewController中重寫viewWillAppear,Run起來看看輸出吧!
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
4)關(guān)聯(lián)屬性
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //相當(dāng)于屬性中的assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //retain,monatomic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy,nonatomic
OBJC_ASSOCIATION_RETAIN = 01401, //retain
OBJC_ASSOCIATION_COPY = 01403 //copy
};
//添加關(guān)聯(lián)對(duì)象
- (void)addAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//獲取關(guān)聯(lián)對(duì)象
- (id)getAssociatedObject{
return objc_getAssociatedObject(self, _cmd);
}
//樣例
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.backgroundColor = [UIColor blackColor];
btn.frame = CGRectMake(100, 100, 60, 30);
[self.view addSubview:btn];
objc_setAssociatedObject(btn, myBtnKey, @"mybtn", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnClick:(id)sender {
NSString *str = objc_getAssociatedObject(sender, myBtnKey);
/**
* CODE
*/
}
5)方法攔截
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;

圖片.png
_objc_msgForward是 IMP類型,用于消息轉(zhuǎn)發(fā)的:當(dāng)向一個(gè)對(duì)象發(fā)送一條消息,但它并沒有實(shí)現(xiàn)的時(shí)候,_objc_msgForward會(huì)嘗試做消息轉(zhuǎn)發(fā)。
IMP msgForward = _objc_msgForward;
如果手動(dòng)調(diào)用objc_msgForward,將跳過查找IMP的過程,而是直接出發(fā)“消息轉(zhuǎn)發(fā)”,進(jìn)入如下流程:
1)+ (BOOL)resolveInstanceMethodL:(SEL)sel 實(shí)現(xiàn)方法,指定是否動(dòng)態(tài)添加方法。若返回NO,則進(jìn)入下一步,若返回YES,則通過class_addMethod函數(shù)動(dòng)態(tài)地添加方法,消息得到處理,此流程完畢。
2)在第一步返回的是NO時(shí),就會(huì)進(jìn)入 -(id)forwardTargetForSelector:(SEL)aSelector 方法,這是運(yùn)行時(shí)給我們的第二次機(jī)會(huì),用于指定哪個(gè)對(duì)象響應(yīng)這個(gè)selector。不能指定為self。若返回nil,表示沒有響應(yīng)者,則會(huì)進(jìn)入第三不。若返回某個(gè)對(duì)象,則會(huì)調(diào)用該對(duì)象的方法。
3)若第二部返回的是nil,則我們首先要通過 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 指定方法簽名,若返回nil,則表示不處理。若返回方法簽名,則會(huì)進(jìn)入下一步。
4)當(dāng)?shù)谌椒呕胤椒ê灻螅蜁?huì)調(diào)用 -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,我們可以通過anInvocation對(duì)象做很多處理,比如修改實(shí)現(xiàn)方法,修改響應(yīng)對(duì)象等。
5)若沒有實(shí)現(xiàn) -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,那么會(huì)進(jìn)入 -(void)doesNotRecognizeSelector:(SEL)aSelector方法。若我們沒有實(shí)現(xiàn)這個(gè)方法,那么就會(huì)crash,然后提示打不到響應(yīng)的方法。到此,動(dòng)態(tài)解析的流程就結(jié)束了。
6)runtime如何實(shí)現(xiàn)weak變量的自動(dòng)置nil?
runtime對(duì)注冊(cè)的類會(huì)進(jìn)行布局,對(duì)于weak對(duì)象會(huì)放入一個(gè)hash表中。用weak指向的對(duì)象內(nèi)存地址作為key,當(dāng)此對(duì)象的引用計(jì)數(shù)為0的時(shí)候會(huì)dealloc。假如weak指向的對(duì)象內(nèi)存地址是a,那么就會(huì)以a為鍵,在這個(gè)weak表中搜索,找到所有以a為鍵的weak對(duì)象,從而設(shè)置為nil。
weak修飾的指針默認(rèn)值是nil(在Objective-C中向nil發(fā)送消息是安全的)