歡迎轉(zhuǎn)載镣奋,請注明出處:
http://zyden.vicp.cc/about-objcruntime/
謝謝
前言:
陳列一下今天要講的知識點(diǎn):class_addMethod澜术,class_replaceMethod钢颂,method_getImplementation,object_getClass
涉及到的知識
--使用category白嘁,通過Runtime實(shí)現(xiàn)用自己的函數(shù)調(diào)換掉原生函數(shù)
--oc的message forwarding
--使用Runtime為類添加原來沒有的方法
--為什么category里不重寫方法
注明:
本文章內(nèi)技術(shù)參考當(dāng)然來自四面八方心墅,來自不同時(shí)期,小弟只是做個(gè)總結(jié)持灰,有不好的地方歡迎大家指導(dǎo)
先從一個(gè)場景問題帶出吧盔夜,畢業(yè)設(shè)計(jì)的時(shí)候小弟做ipad應(yīng)用,到后面才決定加上旋轉(zhuǎn)屏適配堤魁,看著100多個(gè)文件20多個(gè)頁面差點(diǎn)沒把血吐出來喂链,哈哈每個(gè)controller去修改方法是不可能的了,因?yàn)閺?qiáng)迫癥也不想多創(chuàng)個(gè)父類妥泉,好吧決定一次過替換掉這些controller里的viewWillAppear: 和 willAnimateRotationToInterfaceOrientation:duration:椭微,換成自己的。
先看一個(gè)category
通過運(yùn)用class_addMethod和class_replaceMethod來調(diào)換掉系統(tǒng)庫里的方法
#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ù)涛漂,原方法選擇子赏表,新方法選擇子,并通過class_getInstanceMethod()拿到對應(yīng)的Method
2.class_addMethod,是相對于實(shí)現(xiàn)來的說的匈仗,將本來不存在于被操作的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)在通過舊方法SEL來調(diào)用何鸡,就會實(shí)現(xiàn)新方法的IMP纺弊,通過新方法的SEL來調(diào)用,就會實(shí)現(xiàn)舊方法的IMP骡男,好了理一理思路繼續(xù)往下淆游。
這次就用NSString做載體來演示吧:
#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(@"我是動態(tài)加入的函數(shù)");
}
@end
1.首先這里要提下resolveInstanceMethod:,不了解的朋友可以去補(bǔ)一下oc的message forwarding,就是當(dāng)運(yùn)行時(shí)對象調(diào)用了一個(gè)找不到的方法的時(shí)候系統(tǒng)會去尋找的機(jī)制,這個(gè)方法是第一步去到的地方犹菱,我們可以在這里面runtime添加方法拾稳,是的,首先我們得劫持了這個(gè)方法腊脱,做我們自己的事访得,通過剛才category里封裝好的swizzleMethod:withMethod:
-------這個(gè)時(shí)候有朋友有疑問了,我們可以重寫這個(gè)方法來做自己的事情啊陕凹,其實(shí)并不可以悍抑,在category里重寫現(xiàn)有方法會有警告#Category is implementing a method which will also be implemented by its primary class,這種做法是不提倡的捆姜!
------------category沒有辦法去代替子類传趾,它不能像子類一樣通過super去調(diào)用父類的方法實(shí)現(xiàn)。如果category中重寫覆蓋了當(dāng)前類中的某個(gè)方法泥技,那么這個(gè)當(dāng)前類中的原始方法實(shí)現(xiàn)浆兰,將永遠(yuǎn)不會被執(zhí)行,這在某些方法里是致命的(這里提一下一個(gè)特例+(void)load珊豹,它會在當(dāng)前方法里執(zhí)行完再去category里執(zhí)行).
------------如果兩個(gè)category重寫了同一個(gè)方法簸呈,我們無法控制哪個(gè)優(yōu)先級更高蜕便,一直以來還是提倡通過繼承去重寫方法
2.object_getClass拿到當(dāng)前MyString的Class,調(diào)用剛才category里封裝好的swizzleMethod:withMethod:贩幻,用我們自己的myResolveInstanceMethod:去替換原生的丛楚,好了,現(xiàn)在如果我們在運(yùn)行時(shí)調(diào)用了一個(gè)不存在的方法仿荆,系統(tǒng)會去調(diào)用我們的myResolveInstanceMethod:,是的不用懷疑拢操。
3.現(xiàn)在看看myResolveInstanceMethod:里面又調(diào)用了一次myResolveInstanceMethod:令境,有的朋友會以為是遞歸其實(shí)并不是展父,系統(tǒng)去調(diào)用原生的方法返劲,會跑到我們自己的方法實(shí)現(xiàn),是因?yàn)槲覀冎暗膕wizzle操作沒問題吕漂,而不要忘記了惶凝,我們自己的方法selector對應(yīng)的實(shí)現(xiàn),已經(jīng)換成了原生方法的實(shí)現(xiàn)苍鲜,ok混滔。歹颓。if(! [self myResolveInstanceMethod:sel])是調(diào)用原生方法的實(shí)現(xiàn),去檢測一次傳入的方法是否存在领跛,如果還是沒有吠昭,則做class_addMethod操作為此類添加對應(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類型對象恩脂,:代表選擇子趣斤。
why? 其實(shí)每一個(gè)oc方法都有兩個(gè)隱式的參數(shù)(id self, SEL _cmd),也可以說是由C語言函數(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] 我是動態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] 我是動態(tài)加入的函數(shù)
2015-10-29 18:12:56.815 ObjcRuntimeDemo[8875:563683] finish test