世上只有兩種編程語言:一種是總是被人罵的,一種是從來沒人用的突倍。 — Bjarne Stroustrup
因為項目的原因,又加上沒有好的題材萨醒,所以最近一段時間都沒有寫一些東西出來拼坎。難得的是這幾天一期需求剛結(jié)束,乘著研究新需求的機(jī)會媚污,又重新去了解了一下RunTime,感覺受益還是挺大的廷雅。
RunTime:簡單的翻譯過來也就是運行時的意思耗美,主要是以c和匯編語言編寫出來的一套方便程序員調(diào)用函數(shù)的庫,其本質(zhì)意義也就是方便消息的傳遞航缀。
一商架、消息傳遞的過程
在object-c中,消息直到運行的時候才會綁定對應(yīng)的實現(xiàn),也就是說一開始方法和方法的實現(xiàn)是互相拆分開來的芥玉,并且object-c中也是允許我們來對其進(jìn)行自由組合蛇摸。
objc_msgSend(class, selector)//or objc_msgSend(class, selector, arg1, arg2, ...)
**SEL : **類成員方法的指針,但不同于C語言中的函數(shù)指針灿巧,函數(shù)指針直接保存了方法的地址赶袄,但SEL只是方法編號揽涮。
IMP:一個函數(shù)指針,保存了方法的地址
函數(shù)指針:一個函數(shù)總是占用一段連續(xù)的內(nèi)存區(qū)域,函數(shù)名在表達(dá)式中有時也會被轉(zhuǎn)換為該函數(shù)所在內(nèi)存區(qū)域的首地址饿肺,這和數(shù)組名非常類似蒋困。我們可以把函數(shù)的這個首地址(或稱入口地址)賦予一個指針變量,使指針變量指向函數(shù)所在的內(nèi)存區(qū)域唬格,然后通過指針變量就可以找到并調(diào)用該函數(shù)家破。
這里有必要再提一下,要實現(xiàn)消息的傳遞關(guān)鍵在于每個繼承于NSObject的類都能自動獲得runtime的支持购岗。每個類中都存在兩個重要的元素:
isa指針汰聋,指向該類定義的數(shù)據(jù)結(jié)構(gòu)體,這個結(jié)構(gòu)體是由編譯器編譯時為類(需繼承于NSObject)創(chuàng)建的.在這個結(jié)構(gòu)體中有包括了指向其父類類定義的指針
類的分發(fā)表( dispatch table),該表包含selector的名稱及對應(yīng)實現(xiàn)函數(shù)的地址
消息執(zhí)行的時候喊积,首先根據(jù)傳遞對象的isa指針找到類的結(jié)構(gòu)烹困,每個類中都存在一個單獨的cache或methodlist,它可以緩存繼承或自定義的方法乾吻。根據(jù)selector名字會首先檢查class類的cache是否已經(jīng)緩存對應(yīng)的selector髓梅,如果有就直接調(diào)用對應(yīng)的實現(xiàn)IMP
如果在cache中找不到就會在其分發(fā)表中尋找對應(yīng)的selector,找到的話就調(diào)用對應(yīng)的實現(xiàn)绎签;找不到則會根據(jù)super class指針去其父類尋找枯饿,如果父類還找不到,會接著去父類的父類中尋找诡必,直到NSObject類為止
如果沒有找到奢方,并且實現(xiàn)了動態(tài)方法決議機(jī)制就會決議:resolveInstanceMethod和resolveClassMethod
如果沒有實現(xiàn)動態(tài)決議機(jī)制或者決議失敗且實現(xiàn)了消息轉(zhuǎn)發(fā)機(jī)制。就會進(jìn)入消息轉(zhuǎn)發(fā)流程爸舒。否則程序Crash(如果同時實現(xiàn)了動態(tài)決議和消息轉(zhuǎn)發(fā)蟋字。那么動態(tài)決議先于消息轉(zhuǎn)發(fā)。只有當(dāng)動態(tài)決議無法決議selector的實現(xiàn)扭勉,才會嘗試進(jìn)行消息轉(zhuǎn)發(fā)鹊奖。)
二、 獲取函數(shù)地址
通過直接獲取函數(shù)指針來繞過消息的綁定實現(xiàn)對函數(shù)的直接調(diào)用涂炎,可以節(jié)約消息傳遞的時間忠聚。
methodForSelector:方法你可以獲取selector對應(yīng)實現(xiàn)的指針,該指針必須轉(zhuǎn)換成合適的函數(shù)類型:
SEL aSelector = @selector(setFilled:);//設(shè)置對應(yīng)的函數(shù)指針類型
/*
使用methodForSelector:繞開動態(tài)綁定節(jié)約了消息傳遞時間
*/
IMP aIMP = [self methodForSelector:aSelector];
void (*setter)(id, SEL, NSString *) = (void(*)(id, SEL, NSString *))aIMP;
setter(self, aSelector, @"哈哈");//通過函數(shù)指針調(diào)用對應(yīng)實現(xiàn)
三唱捣、 消息的動態(tài)決議
通過resolveInstanceMethod:和 resolveClassMethod:動態(tài)的為selector提供實現(xiàn)方法两蟀,在這里你可以通過方法名來對消息的傳遞進(jìn)行攔截來實現(xiàn)你的一些特殊需求:比如對無法處理消息的crash攔截處理或者替換系統(tǒng)API。
objective-c方法本質(zhì)上就是一個帶有至少兩個參數(shù)(_self和_cmd)的c函數(shù)爷光,你可以通過 class_addMethod為類添加一個函數(shù)
#import "SomeClass.h"
#import <objc/runtime.h>
static char * ObjectTagKey = "";
@interface SomeClass ()
@end
@implementation SomeClass{
}
//@dynamic objectTag;
#pragma mark - 動態(tài)決議
/**
添加setter實現(xiàn)
*/
void dynamicSetMethod(id self, SEL _cmd, float w) {
printf("dynamicSetMeghod-%s\n",[NSStringFromSelector(_cmd) cStringUsingEncoding:NSUTF8StringEncoding]);
printf("%f\n",w);
objc_setAssociatedObject(self, ObjectTagKey, [NSNumber numberWithFloat:w], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@" >> dynamicMethodIMP");
}
/**
添加getter實現(xiàn)
*/
void dynamicGetMethod(id self, SEL _cmd) {
printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd)
cStringUsingEncoding:NSUTF8StringEncoding]);
objc_getAssociatedObject(self, ObjectTagKey);
}
/**
解析selector方法(動態(tài)方法決議,對象方法)
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
BOOL result = NO;
/*
動態(tài)的添加setter和getter方法
*/
if ([methodName isEqualToString:@"setObjectTag:"]) {
class_addMethod([self class], sel, (IMP)dynamicSetMethod, "v@:f");
result = YES;
}else if ([methodName isEqualToString:@"objectTag"]){
class_addMethod([self class], sel, (IMP)dynamicGetMethod, "v@:f");
result = YES;
}
return result;
}
/**
解析selector方法(動態(tài)方法決議,類方法)
*/
+ (BOOL)resolveClassMethod:(SEL)sel {
return [super resolveClassMethod:sel];
}
/**
動態(tài)決議調(diào)用
*/
- (void)resolveBindMethod {
SomeClass *objec = [[SomeClass alloc]init];
objec.objectTag = 10.0f;
float tag = objec.objectTag;
}
四、 消息的轉(zhuǎn)發(fā)
每一個對象都從NSObject類繼承了forwardInvocation:方法澎粟,但在NSObject中蛀序,該方法只是簡單的調(diào)用doesNotRecognizeSelector:欢瞪,通過重寫該方法你就可以利用forwardInvocation:將消息轉(zhuǎn)發(fā)給其它對象。
- 先調(diào)用methodSignatureForSelector:獲取指定selector的方法簽名
- 重寫forwardInvocation方法徐裸,調(diào)用invokeWithTarget來確定消息轉(zhuǎn)發(fā)的對象和對應(yīng)的原始參數(shù)
#import "SomeClass.h"
#import <objc/runtime.h>
#import "ForwardClass.h"
@implementation SomeClass{
id forwardClass;
}
- (id)init {
if (self = [super init]) {
forwardClass = [ForwardClass new];
}
return self;
}
#pragma mark - 消息轉(zhuǎn)發(fā)
/**
消息轉(zhuǎn)發(fā)
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if (!forwardClass) {
[self doesNotRecognizeSelector:[anInvocation selector]];
}
[anInvocation invokeWithTarget:forwardClass];
}
/**
消息轉(zhuǎn)發(fā)前先執(zhí)行此方法
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
//生成方法簽名
signature = [forwardClass methodSignatureForSelector:aSelector];
}
return signature;
}
/**
消息轉(zhuǎn)發(fā)調(diào)用
*/
- (void)forwardMethod {
SomeClass *objec = [SomeClass new];
SEL aselector = @selector(doSomethingElse);
IMP aimp = [objec methodForSelector:aselector];
void (*setter)(id, SEL) = (void (*)(id, SEL))aimp;
setter(objec, aselector);
}
消息轉(zhuǎn)發(fā)有很多的用途遣鼓,比如:
- 創(chuàng)建一個對象負(fù)責(zé)把消息轉(zhuǎn)發(fā)給一個由其它對象組成的響應(yīng)鏈,代理對象會在這個有其它對象組成的集合里尋找能夠處理該消息的對象重贺;
- 把一個對象包在一個logger對象里骑祟,用來攔截或者紀(jì)錄一些有趣的消息調(diào)用;
- 比如聲明類的屬性為dynamic气笙,使用自定義的方法來截取和取代由系統(tǒng)自動生成的getter和setter方法次企。
五、 Method Swizzling
Method Swizzling:被灌醉的方法
前面也曾提到過潜圃,object-c中是允許我們來對方法和方法的實現(xiàn)進(jìn)行自由組合的缸棵,原理也就是替換掉對應(yīng)方法的函數(shù)指針I(yè)MP,達(dá)到另類的方法實現(xiàn)
通過RunTime的Swizzling我們可以做很多事情:比如目前流行的無埋點統(tǒng)計中的事件圈選以及APM的頁面慢交互分析谭期,底層都是通過改變類的分發(fā)表里selector和實現(xiàn)之間的對應(yīng)關(guān)系堵第,來進(jìn)行信息捕獲的
#import "UIView+Hook.h"
#import <objc/runtime.h>
static NSHashTable *hashTable;
@implementation UIView (Hook)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL originalSelector = @selector(init);
SEL swizzledSelector = @selector(customInit);
Method originalMethod = class_getInstanceMethod([self class], originalSelector);
Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
BOOL didAddMethod = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
}else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (instancetype)customInit {
if (!hashTable) {
hashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
}
[hashTable addObject:self];
return [self customInit];
}
- (NSArray *)showRecords {
NSLog(@"records==>%@",hashTable.allObjects);
return hashTable.allObjects;
}
以上所說的主要還是針對RunTime的消息傳遞來說的,其實RunTime的用途還有很多隧出,一些基礎(chǔ)的class_copyIvarList踏志、class_copyMethodList、class_copyProtocolList胀瞪、class_copyPropertyList的使用在這里就不在多說了针余,有興趣的同學(xué)可以自行在網(wǎng)上查找。