方法的實質
在OC中,方法的實質其實是兩部分組成:
1.方法的代號(SEL),
2.方法的實現(xiàn)(IMP),
對象調用方法,實際上就是一個發(fā)送消息的過程.
比如[person eat];其實等價于:
objc_msgSend(person, @selector(eat));
或
objc_msgSend(person, NSSelectorFromString(@"eat"));
或
objc_msgSend(person, sel_registerName("eat"));
再比如我們最熟悉的初始化方法
Person *person = [[Person alloc]init];
在底層其實是這樣實現(xiàn)的:
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
ps:為能編譯過需要把 Buid Setting? msg 設置為NO并且導入<objc/runtime.h>
Runtime的作用
上面其實就是調用了蘋果提供的RuntimeAPI,即程序運行時.對于我們開發(fā)者來講,其實主要有3個用處:
1.在程序運行的過程中,動態(tài)的創(chuàng)建一個類
2.在程序運行的過程中,動態(tài)的為某個類添加/修改方法/屬性
3.在程序運行的過程中,動態(tài)的遍歷類的所有方法和成員變量
下面其實都是runtime這些用法的實現(xiàn).
方法的"懶加載"
首先我們知道,NSObject有如下兩個方法:
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
前者是當類調用了沒有實現(xiàn)的(就是缺少sel/imp)類方法時調用,后者則是調用了未實現(xiàn)的實例方法時調用.
例如:上面的Person * p = [[Person alloc]init];
我并沒有實現(xiàn)run:方法,但是當我
?? [p performSelector:@selector(run:) withObject:@"100"];
程序當然毫無懸念的崩潰了??.
然而現(xiàn)在操蛋的需求是,讓person表面上在跑,實際上在飛,程序還不能崩...這怎么可能!!!!!!?
但是,為了實現(xiàn)需求,我突然想到之前提到的方法:
#import "Person.h"
@implementation Person
//當類調用了沒有實現(xiàn)的實例方法,會到這來
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//這是添加方法實現(xiàn),類似懶加載
//prama:1.類,2.方法編號SEL,3.方法實現(xiàn)IMP(函數(shù)指針),4.返回值類型(可以為"")
class_addMethod(self,? sel, (IMP)fly, "v@:@");
return [super resolveInstanceMethod:sel];
}
void fly(id self,SEL _cmd,NSString* distance){
NSLog(@"我就想飛!飛了%@米",distance);
};
以上代碼的原理是:
我們調用run:方法其實是發(fā)消息:
objc_msgSend(p, @selector(run:), @"100");
所以實現(xiàn)fly函數(shù)時,要接收(p, @selector(run:), @"100")這3個參數(shù)
這時就實現(xiàn)了"我就想飛!飛了100米",因為fly是當我們調用時才去加載的,類似于屬性的懶加載,故而我們可以說是方法的"懶加載"
ps:我們可以發(fā)現(xiàn)OC方法調用時會默認傳遞兩個參數(shù),id self 和 方法代號 sel.
Hook(鉤子)
runtime最常見的用法,實現(xiàn)方法的欺騙,直接上代碼:
//在load里下鉤子(load = 編譯前加載)
+ (void)load{
//方法欺騙(替換)
//本來的方法
Method sysMethod = class_getClassMethod([NSURL class], @selector(URLWithString:));
//自定義的方法
Method bbMethod = class_getClassMethod([NSURL class], @selector(BBURLWithString:));
//交換
method_exchangeImplementations(sysMethod, bbMethod);
}
//改URLWithString:方法,能判斷空的URL,如果為空返回百度 (交換后如果再掉系統(tǒng)方法會遞歸奔潰 所以需要寫注釋)
+ (instancetype)BBURLWithString:(NSString *)URLString{
NSURL *url = [NSURL BBURLWithString:URLString];
if (url == nil || [URLString isEqualToString:@""]) NSLog(@"url 為 空");
return [NSURL URLWithString:@"http://www.baidu.com"];
}
return url;
}
KVO
我們都知道,一個KVO的實現(xiàn),實際上就是觀察屬性的setter方法,因此我們實現(xiàn)一個KVO需要幾個步驟:
1.動態(tài)派生 類的子類(NSKVONotyfing_person)
2.重寫setter方法
3.方法中
[self willChangeValueForKey:@"name"]
[super setName:name];
[self didChangeValueForKey:@"name"];
未完待續(xù)................................