方法
定義
typedef struct objc_method *Method
方法調(diào)用
1各聘、如果用實(shí)例對(duì)象調(diào)用實(shí)例方法吭历,會(huì)到實(shí)例的isa指針指向的對(duì)象(也就是類(lèi)對(duì)象)操作。
2呼渣、如果調(diào)用的是類(lèi)方法棘伴,就會(huì)到類(lèi)對(duì)象的isa指針指向的對(duì)象(也就是元類(lèi)對(duì)象)中操作。
首先屁置,在相應(yīng)操作的對(duì)象中的緩存方法列表中找調(diào)用的方法焊夸,如果找到,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)并執(zhí)行缰犁。
3淳地、如果沒(méi)找到,在相應(yīng)操作的對(duì)象中的方法列表中找調(diào)用的方法帅容,如果找到颇象,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)執(zhí)行
如果沒(méi)找到,去父類(lèi)指針?biāo)赶虻膶?duì)象中執(zhí)行1并徘,2.
4遣钳、以此類(lèi)推,如果一直到根類(lèi)還沒(méi)找到麦乞,轉(zhuǎn)向攔截調(diào)用蕴茴。
5、如果沒(méi)有重寫(xiě)攔截調(diào)用的方法姐直,程序報(bào)錯(cuò)倦淀。
重寫(xiě)父類(lèi)的方法,并沒(méi)有覆蓋掉父類(lèi)的方法声畏,只是在當(dāng)前類(lèi)對(duì)象中找到了這個(gè)方法后就不會(huì)再去父類(lèi)中找了撞叽。
如果想調(diào)用已經(jīng)重寫(xiě)過(guò)的方法的父類(lèi)的實(shí)現(xiàn)姻成,只需使用super這個(gè)編譯器標(biāo)識(shí),它會(huì)在運(yùn)行時(shí)跳過(guò)在當(dāng)前的類(lèi)對(duì)象中尋找方法的過(guò)程愿棋。
攔截調(diào)用
攔截調(diào)用就是科展,在找不到調(diào)用的方法程序崩潰之前,你有機(jī)會(huì)通過(guò)重寫(xiě)NSObject的四個(gè)方法來(lái)處理糠雨。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后兩個(gè)方法需要轉(zhuǎn)發(fā)到其他的類(lèi)處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
第一個(gè)方法是當(dāng)你調(diào)用一個(gè)不存在的類(lèi)方法的時(shí)候才睹,會(huì)調(diào)用這個(gè)方法,默認(rèn)返回NO甘邀,你可以加上自己的處理然后返回YES琅攘。
第二個(gè)方法和第一個(gè)方法相似,只不過(guò)處理的是實(shí)例方法鹃答。
第三個(gè)方法是將你調(diào)用的不存在的方法重定向到一個(gè)其他聲明了這個(gè)方法的類(lèi)乎澄,只需要你返回一個(gè)有這個(gè)方法的target。
第四個(gè)方法是將你調(diào)用的不存在的方法打包成NSInvocation傳給你测摔。做完你自己的處理后置济,調(diào)用invokeWithTarget:方法讓某個(gè)target觸發(fā)這個(gè)方法。
動(dòng)態(tài)添加方法
重寫(xiě)了攔截調(diào)用的方法并且返回了YES锋八,我們要怎么處理呢浙于?
有一個(gè)辦法是根據(jù)傳進(jìn)來(lái)的SEL類(lèi)型的selector動(dòng)態(tài)添加一個(gè)方法。
首先從外部隱式調(diào)用一個(gè)不存在的方法:
//隱式調(diào)用方法
[target performSelector:@selector(resolveAdd:) withObject:@"test"];
然后挟纱,在target對(duì)象內(nèi)部重寫(xiě)攔截調(diào)用的方法羞酗,動(dòng)態(tài)添加方法。
void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP ", string);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
//給本類(lèi)動(dòng)態(tài)添加一個(gè)方法
if ([NSStringFromSelector(sel) ?isEqualToString: @"resolveAdd:"]) {
? ? ?class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
return YES;
}
其中class_addMethod的四個(gè)參數(shù)分別是:
Class cls給哪個(gè)類(lèi)添加方法紊服,本例中是self
SEL name添加的方法檀轨,本例中是重寫(xiě)的攔截調(diào)用傳進(jìn)來(lái)的selector。
IMP imp方法的實(shí)現(xiàn)欺嗤,C方法的方法實(shí)現(xiàn)可以直接獲得参萄。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;獲得方法的實(shí)現(xiàn)煎饼。
"v@:*"方法的簽名讹挎,代表有一個(gè)參數(shù)的方法。
方法交換
方法交換吆玖,顧名思義筒溃,就是將兩個(gè)方法的實(shí)現(xiàn)交換。例如沾乘,將A方法和B方法交換怜奖,調(diào)用A方法的時(shí)候,就會(huì)執(zhí)行B方法中的代碼翅阵,反之亦然烦周。
話不多說(shuō)尽爆,這是參考Mattt大神在NSHipster上的文章自己寫(xiě)的代碼怎顾。
#import "UIViewController+swizzling.h"
#import
@implementation UIViewController (swizzling)
//load方法會(huì)在類(lèi)第一次加載的時(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) {
//如果成功,說(shuō)明類(lèi)中不存在這個(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)用自己株灸,看起來(lái)像是死循環(huán)
//但是其實(shí)自己的實(shí)現(xiàn)已經(jīng)被替換了
[self swiz_viewWillAppear:animated];
NSLog(@"swizzle");
}
@end
在一個(gè)自己定義的viewController中重寫(xiě)viewWillAppear
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}