歡迎轉(zhuǎn)載,請(qǐng)注明出處:
http://zyden.vicp.cc/about-objcruntime/
謝謝
前言:
陳列一下今天要講的知識(shí)點(diǎn):class_addMethod,class_replaceMethod,method_getImplementation,object_getClass
涉及到的知識(shí)
--使用category,通過(guò)Runtime實(shí)現(xiàn)用自己的函數(shù)調(diào)換掉原生函數(shù)
--oc的message forwarding
--使用Runtime為類添加原來(lái)沒(méi)有的方法
--為什么category里不重寫方法
注明:
本文章內(nèi)技術(shù)參考當(dāng)然來(lái)自四面八方,來(lái)自不同時(shí)期,小弟只是做個(gè)總結(jié),有不好的地方歡迎大家指導(dǎo)
先從一個(gè)場(chǎng)景問(wèn)題帶出吧,畢業(yè)設(shè)計(jì)的時(shí)候小弟做ipad應(yīng)用,到后面才決定加上旋轉(zhuǎn)屏適配,看著100多個(gè)文件20多個(gè)頁(yè)面差點(diǎn)沒(méi)把血吐出來(lái),哈哈每個(gè)controller去修改方法是不可能的了,因?yàn)閺?qiáng)迫癥也不想多創(chuàng)個(gè)父類,好吧決定一次過(guò)替換掉這些controller里的viewWillAppear: 和 willAnimateRotationToInterfaceOrientation:duration:,換成自己的。
先看一個(gè)category
通過(guò)運(yùn)用class_addMethod和class_replaceMethod來(lái)調(diào)換掉系統(tǒng)庫(kù)里的方法
#import "NSObject+Swizzle.h"
@implementation NSObject (Swizzle)
+ (BOOL)swizzleMethod:(SEL)origSel withMethod:(SEL)aftSel {
Method originMethod = class_getInstanceMethod(self, origSel);
Method newMethod = class_getInstanceMethod(self, aftSel);
if(originMethod && newMethod) {//必須兩個(gè)Method都要拿到
if(class_addMethod(self, origSel, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
//實(shí)現(xiàn)成功添加后
class_replaceMethod(self, aftSel, method_getImplementation(originMethod), method_getTypeEncoding(originMethod));
}
return YES;
}
return NO;
}
@end
1.傳入兩個(gè)參數(shù),原方法選擇子,新方法選擇子,并通過(guò)class_getInstanceMethod()拿到對(duì)應(yīng)的Method
2.class_addMethod,是相對(duì)于實(shí)現(xiàn)來(lái)的說(shuō)的,將本來(lái)不存在于被操作的Class里的newMethod的實(shí)現(xiàn)添加在被操作的Class里,并使用origSel作為其選擇子(注意參數(shù)中的self為被操作的Class,不要忘了這里是類方法).
3.class_replaceMethod,addMethod成功完成后,從參數(shù)可以看出,目的是換掉method_getImplaementation(roiginMethod)的選擇子,將原方法的實(shí)現(xiàn)的SEL換成新方法的SEL:aftSel,ok目的達(dá)成了。想一想,現(xiàn)在通過(guò)舊方法SEL來(lái)調(diào)用,就會(huì)實(shí)現(xiàn)新方法的IMP,通過(guò)新方法的SEL來(lái)調(diào)用,就會(huì)實(shí)現(xiàn)舊方法的IMP,好了理一理思路繼續(xù)往下。
這次就用NSString做載體來(lái)演示吧:
#import "MyString.h"
#import "NSObject+Swizzle.h"
@implementation MyString
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class clazz = object_getClass((id)self);
[clazz swizzleMethod:@selector(resolveInstanceMethod:) withMethod:@selector(myResolveInstanceMethod:)];
});
}
+ (BOOL)myResolveInstanceMethod:(SEL)sel {
if(! [self myResolveInstanceMethod:sel]) {
NSString *selString = NSStringFromSelector(sel);
if([selString isEqualToString:@"countAll"] || [selString isEqualToString:@"pushViewController"]) {
class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");
return YES;
}else {
return NO;
}
}
return YES;
}
- (void)dynamicMethodIMP {
NSLog(@"我是動(dòng)態(tài)加入的函數(shù)");
}
@end
1.首先這里要提下resolveInstanceMethod:,不了解的朋友可以去補(bǔ)一下oc的message forwarding,就是當(dāng)運(yùn)行時(shí)對(duì)象調(diào)用了一個(gè)找不到的方法的時(shí)候系統(tǒng)會(huì)去尋找的機(jī)制,這個(gè)方法是第一步去到的地方,我們可以在這里面runtime添加方法,是的,首先我們得劫持了這個(gè)方法,做我們自己的事,通過(guò)剛才category里封裝好的swizzleMethod:withMethod:
-------這個(gè)時(shí)候有朋友有疑問(wèn)了,我們可以重寫這個(gè)方法來(lái)做自己的事情啊,其實(shí)并不可以,在category里重寫現(xiàn)有方法會(huì)有警告#Category is implementing a method which will also be implemented by its primary class,這種做法是不提倡的!
------------category沒(méi)有辦法去代替子類,它不能像子類一樣通過(guò)super去調(diào)用父類的方法實(shí)現(xiàn)。如果category中重寫覆蓋了當(dāng)前類中的某個(gè)方法,那么這個(gè)當(dāng)前類中的原始方法實(shí)現(xiàn),將永遠(yuǎn)不會(huì)被執(zhí)行,這在某些方法里是致命的(這里提一下一個(gè)特例+(void)load,它會(huì)在當(dāng)前方法里執(zhí)行完再去category里執(zhí)行).
------------如果兩個(gè)category重寫了同一個(gè)方法,我們無(wú)法控制哪個(gè)優(yōu)先級(jí)更高,一直以來(lái)還是提倡通過(guò)繼承去重寫方法
2.object_getClass拿到當(dāng)前MyString的Class,調(diào)用剛才category里封裝好的swizzleMethod:withMethod:,用我們自己的myResolveInstanceMethod:去替換原生的,好了,現(xiàn)在如果我們?cè)谶\(yùn)行時(shí)調(diào)用了一個(gè)不存在的方法,系統(tǒng)會(huì)去調(diào)用我們的myResolveInstanceMethod:,是的不用懷疑。
3.現(xiàn)在看看myResolveInstanceMethod:里面又調(diào)用了一次myResolveInstanceMethod:,有的朋友會(huì)以為是遞歸其實(shí)并不是,系統(tǒng)去調(diào)用原生的方法,會(huì)跑到我們自己的方法實(shí)現(xiàn),是因?yàn)槲覀冎暗膕wizzle操作沒(méi)問(wèn)題,而不要忘記了,我們自己的方法selector對(duì)應(yīng)的實(shí)現(xiàn),已經(jīng)換成了原生方法的實(shí)現(xiàn),ok。。if(! [self myResolveInstanceMethod:sel])是調(diào)用原生方法的實(shí)現(xiàn),去檢測(cè)一次傳入的方法是否存在,如果還是沒(méi)有,則做class_addMethod操作為此類添加對(duì)應(yīng)的方法,return YES,該方法被系統(tǒng)調(diào)用,OK,達(dá)到目的。
class_addMethod參數(shù)的意義
class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
class_addMethod(self, sel, class_getMethodImplementation(self, @selector(dynamicMethodIMP)), "v@:");
按順序是,類--選擇子--實(shí)現(xiàn)--方法的返回值和參數(shù)資料。
v代表返回值void,@代表id類型對(duì)象,:代表選擇子。
why? 其實(shí)每一個(gè)oc方法都有兩個(gè)隱式的參數(shù)(id self, SEL _cmd),也可以說(shuō)是由C語(yǔ)言函數(shù)再加著兩個(gè)參數(shù)組成一個(gè)oc方法。
最后看看我們的工作的收獲:
NSLog(@"begin test");
//------------------------------------------------
MyString *string = [[MyString alloc] init];
[string performSelector:@selector(countAll)];
[string performSelector:@selector(pushViewController)];
<pre name="code" class="objc">
//------------------------------------------------
NSLog(@"finish test");
-----Log:
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] begin test
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動(dòng)態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動(dòng)態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動(dòng)態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test