最近在準備明年春招(實驗室是NLP方向。。。平時還得水一水競賽task,哎說多了都是淚) 對於runtime平時用的比較少,只遇到以下幾個狀況: 1,第三方庫的只給了.h文件,要實現方法的拓展(直接觀察,獲取方法列表,而後進行注入),後來發現沒有加密的靜態庫也能夠成功hook?好強大,不敢往下想了,微信自動搶紅包的插件? 2,爲分類添加屬性 3,攔截系統的方法,添加計數等新功能等 可是面試面的真的好好好深啊!正好複習整理下,方便春招和之後查閱。程序員
先上兩張圖幫助理解: runtime:主要用法(Jspatch請忽略,各位儘管Patch。。能經過算個人。。哈哈哈,圖比較老了) 面試
元類-父類以及實體的關係圖:##runtime是什麼 runtime是屬於OC的底層,是一套比較底層的純C語言API, 屬於1個C語言庫, 包含了不少底層的C語言API,能夠進行一些很是底層的操做(用OC是沒法現實的, 很差實現)。 在咱們平時編寫的OC代碼中, 程序運行過程時, 其實最終都是轉成了runtime的C語言代碼, runtime算是OC的幕後工做者。 runtime中的C語言指針不用的時候須要free()掉,ARC對C語言指針沒有辦法,只能經過free()方法。數組
##頭文件:緩存
<objc/runtime.h>
<objc/message.h>
複製代碼
必備常識:bash
//類在runtime中的表示
struct objc_class {
Class isa;//指針,顧名思義,表示是一個什麼,
//實例的isa指向類對象,類對象的isa指向元類
#if !__OBJC2__
Class super_class; //指向父類
const char *name; //類名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成員變量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//緩存,一種優化,調用過的方法存入緩存列表,下次調用先找緩存
struct objc_protocol_list *protocols //協議列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
複製代碼
[target doSomething];
會被轉化成
objc_msgSend(target, @selector(doSomething));
複製代碼
Runtime的主要用法:微信
在OOP術語中,消息傳遞是指一種在對象之間發送和接收消息的通訊模式。 在Objective-C中,消息傳遞用於在調用類和類實例的方法,即接收者接收須要執行的消息。函數
執行實質:優化
若是調用的是類方法: 就會到類對象的isa指針指向的對象(也就是元類對象)中操做。ui
若是是實例方法: 1,先在相應操做的對象中的緩存方法列表中找調用的方法,若是找到,轉向相應實現並執行。 2,若是沒找到,在相應操做的對象中的方法列表中找調用的方法,若是找到,轉向相應實現執行 3,若是沒找到,去父類指針所指向的對象中執行1,2.以此類推,若是一直到根類還沒找到,轉向攔截調用。若是沒有重寫攔截調用的方法,拋出異常程序報錯。this
使用案例:
// 經過類名獲取類
//注意Class實際上也是對象,因此一樣可以接受消息,向Class發送alloc消息
//報錯的話,須要修改Build Setting--> Apple LLVM 6.0 - Preprocessing--> Enable Strict Checking of objc_msgSend Calls 改成 NO
Student *student = [[Student alloc]init];
objc_msgSend(student,@selector(eat));
objc_msgSend(student, @selector(say:),@"這是一句要說的話!");
複製代碼
ivar表示成員變量 class_addIvar class_addMethod class_addProperty class_addProtocol class_replaceProperty
unsigned int count;
//獲取student類的屬性列表
objc_property_t *propertyList = class_copyPropertyList([student class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
free(propertyList);
//獲取本類的方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i= 0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
free(methodList);
//獲取成員變量列表
Ivar *ivarList = class_copyIvarList([student class], &count);
for (unsigned int i= 0; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
free(ivarList);
複製代碼
已知類MyClass具備私有方法methodA;要求如何在不改變MyClass源碼的基礎上擴展methodA方法,使其在執行methodA方法的時候具備新增功能methodB或者 攔截方法調用(Swizzle 黑魔法),也能夠說成進行替換) 好比:跟蹤程序每一個ViewController展現給用戶的次數,能夠經過Method Swizzling替換ViewDidAppear初始方法。 建立一個UIViewController的分類,重寫自定義的ViewDidAppear方法,並在其+load方法中實現ViewDidAppear方法的交換。
實現:藉助Runtime的Method Swizzling(方法替換)以及Associated Object(關聯對象)
Objective-C 提供了一下API用於動態替換類方法或者實例方法的實現:
class_replaceMethod 替換類方法的定義 method_exchangeImplementations 交換兩個方法的實現(具體使用案例以下) method_setImplementation 設置一個方法的實現 注:class_replaceMethod 試圖替換一個不存在的方法時候,會調用class_addMethod爲該類增長一個新方法
替換Student類的方法eat和say爲: 在Student+MyCategory分類中:
//替換方法, 必須在initialize方法中使用
+(void)initialize{
Method eatMethod = class_getInstanceMethod([self class], @selector(eat));
Method newEatMethod =class_getInstanceMethod([self class],@selector(newEat));
//SEL是方法的索引,IMP是真正的函數地址,所以替換函數,只要將SEL索引互換便可達到效果
method_exchangeImplementations(eatMethod, newEatMethod);
Method sayMethod = class_getInstanceMethod([self class], @selector(say:));
Method newSayMethod =class_getInstanceMethod([self class],@selector(newSay));
method_exchangeImplementations(sayMethod, newSayMethod);
}
-(void)newEat{
NSLog(@"NewEat");
}
-(void)newSay{
NSLog(@"NewSay");
}
複製代碼
其中class_addMethod的四個參數分別是:
Class cls 給哪一個類添加方法,本例中是self SEL name 添加的方法,本例中是重寫的攔截調用傳進來的selector。 IMP imp 方法的實現,C方法的方法實現能夠直接得到。若是是OC方法,能夠用+ (IMP)instanceMethodForSelector:(SEL)aSelector;得到方法的實現。 "v@:*"方法的簽名,表明有一個參數的方法。
#pragma mark-運行時添加方法
//c語言方法
void runAddMethod(id self, SEL _cmd, NSString *string){
NSLog(@"add C IMP %@", string);
}
//OC方法
-(void)runAddMethod2{
NSLog(@"add OC IMP");
}
#pragma mark-動態添加方法
-(void)runTimeAddMethod{
class_addMethod([self class], @selector(runAddMethod:), (IMP)runAddMethod, nil);
IMP imp = [self methodForSelector:@selector(runAddMethod2)];
class_addMethod([self class], @selector(runAddMethod2), imp, nil);
objc_msgSend(self, @selector(runAddMethod:),@"this is a func add at runtime");
objc_msgSend(self, @selector(runAddMethod2));
}
複製代碼
添加屬性:(嚴格來講爲分類添加屬性必需要寫setter和getter方法,不寫的話程序內部只能經過runtime進行讀取了)
static char targetKey;
static char actionNameKey;
static char parmatersKey;
-(void)addTarget:(id)target action:(SEL)action paramters:(id)paramters{
//添加屬性
self.target = target;
self.actionName = NSStringFromSelector(action);
self.parameter = paramters;
}
-(void)setTarget:(id)target{
objc_setAssociatedObject(self, &targetKey, target, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id)target{
return objc_getAssociatedObject(self, &targetKey);
}
-(void)setActionName:(NSString *)actionName{
objc_setAssociatedObject(self,&actionNameKey,actionName, OBJC_ASSOCIATION_COPY);
}
-(NSString *)actionName{
return objc_getAssociatedObject(self, &actionNameKey);
}
-(void)setParameter:(id)parameter{
objc_setAssociatedObject(self, &parmatersKey, parameter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(id)parameter{
return objc_getAssociatedObject(self, &parmatersKey);
}
複製代碼
不用對每一個屬性edcode和decode了,若是幾十個屬性一個個的encode和decode真的很麻煩啊,使用運行時能夠遍歷出每一個對象的屬性,數組的方式遍歷eccode,decode 如:
原來歸檔,就比較苦逼了,體力活兒。
若是你給一個對象發送他未定義的消息時,系統會拋出一個錯誤,但在錯誤拋出以前,運行時會給改對象發送forwardInvocation:消息,同時傳遞一個NSInvocation對象做爲該消息的參數,NSInvocation對象包封裝原始消息和對應的參數。你能夠實現forwardInvocation:方法來對不能處理的消息作一些默認的處理,以免程序崩潰,但正如該函數的名字同樣,這個函數主要是用來將消息轉發給其它對象。每個對象都從NSObject類繼承了forwardInvocation:方法,但在NSObject中,該方法只是簡單的調用doesNotRecognizeSelector:,經過重寫該方法你就能夠利用forwardInvocation:將消息轉發給其它對象。
爲了轉發一個消息,forwardInvocation:須要作的事:
forwardInvocation:方法能夠做爲沒法認識的消息的分發中心,將它們打包發給不一樣的接受者,或者做爲一箇中轉站將全部消息發往同一個目的地。它能夠將一個消息轉換成另外的一個消息,或者只是單純的把消息「吞下」,不對外響應和拋出錯誤,forwardInvocation:作什麼取決於實現者。