什么是runtime?
runtime直譯:運(yùn)行時機(jī)制;OC發(fā)送消息的本質(zhì),就是 runtime去調(diào)用蘋果底層的一些函數(shù);
C語言在編譯時,就知道該調(diào)用那些方法, 能成功調(diào)用嗎;
基于runtime的OC語言, 在編譯階段只要方法有聲明,就不會報錯;在運(yùn)行時才會檢測到底有沒有方法,該調(diào)用哪個方法;
查看runtime底層實(shí)現(xiàn):
-
在終端編譯文件 clang -rewrite-objc main.m Person.m 查看最終生成代碼
// 測試代碼
Person *p = [Person alloc];
p = [p init];
生成cpp文件,找到對應(yīng)代碼:
Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc"));
p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("init"));
解析: 簡化代碼后,其實(shí)runtime底層也就是調(diào)用蘋果封裝的方法而已
(Person *(*)(id, SEL))(void *) 強(qiáng)制類型轉(zhuǎn)換, 把objc_msgSend轉(zhuǎn)換成有返回值(Person *), 兩個參數(shù)(id, SEL)的指向函數(shù)的指針;故可去掉,簡化代碼如下:
Person *p = objc_msgSend([Person class], @selector(alloc));
p = objc_msgSend(p, @selector(init));
由于蘋果不推薦我們使用其底層的runtime, 但是有時一些功能只能由runtime實(shí)現(xiàn),故我們首先配置Xcode(以XCocde7為例)
- 導(dǎo)入頭文件#import <objc/message.h>
- 配置文件 : 項(xiàng)目 ---> BuildSetting ----> msg
runtime常用方法:
-
動態(tài)添加方法
- 開發(fā)使用場景:如果一個類方法非常多融师,加載類到內(nèi)存的時候要給每個方法生成相應(yīng)映射表侥蒙,非常耗費(fèi)內(nèi)存資源;這時候可以實(shí)現(xiàn)用動態(tài)給某個類添加方法.
- 比如某應(yīng)用的VIP用戶才能使用一些功能.
代碼實(shí)現(xiàn):
@implementation Person
// 定義函數(shù)
// 默認(rèn)OC方法都有兩個隱式參數(shù),self,_cmd
void run(id self, SEL _cmd) {
NSLog(@"run");
}
// 只要調(diào)用沒有實(shí)現(xiàn)的方法 就會來到方法
// 作用:去解決沒有實(shí)現(xiàn)方法,動態(tài)添加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(run)) {
// 添加方法
class_addMethod(self, sel, (IMP)run, nil);
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
方法解析:
// 添加方法到類
class_addMethod(__unsafe_unretained Class cls, SEL name, IMP imp, const char *types);
class:給誰添加方法
SEL:添加哪個方法
IMP:方法實(shí)現(xiàn),函數(shù)入口,傳入函數(shù)名
type:方法類型 默認(rèn)nil即可
然后我們利用performSelector方法調(diào)用一個沒有實(shí)現(xiàn)的方法:
[p performSelector:@selector(run)]
// 會自動執(zhí)行上面方法添加一個動態(tài)方法run
// 打印輸出:run
tips:
// 沒有實(shí)現(xiàn)對象方法時,調(diào)用該方法
+(BOOL)resolveInstanceMethod:(SEL)sel {
// 添加方法
}
// 沒有實(shí)現(xiàn)類方法時, 調(diào)用該方法
+(BOOL)resolveClassMethod:(SEL)sel {
// 添加方法
}
-
交換兩個方法的實(shí)現(xiàn)
- 需求:想要在調(diào)用imageNamed加載圖片時,提示是否加載成功,
- 特別是大項(xiàng)目時,不希望更改系統(tǒng)方法(得改好多代碼, 工作量忒大...)
此時可以使用runtime的動態(tài)交換方法來實(shí)現(xiàn)功能:
新建一個UIImage的分類
在分類中添加一個該功能的方法;(注意別覆蓋系統(tǒng)方法)
在+load方法中實(shí)現(xiàn)方法的交換
#import "UIImage+image.h"
#import <objc/message.h>
@implementation UIImage (image)
+(void)load {
// 1.0 獲取方法
Method abel_imageNamed = class_getClassMethod(self, @selector(abel_imageNamed:));
Method imageNamed = class_getClassMethod(self, @selector(imageNamed:));
// 2.0 交換方法的實(shí)現(xiàn)
method_exchangeImplementations(abel_imageNamed, imageNamed);
}
// 添加該功能方法
+ (UIImage *)abel_imageNamed:(NSString *)name
{
// 調(diào)用系統(tǒng)方法
UIImage *image = [self abel_imageNamed:name];
// 添加功能
if (image == nil) {
NSLog(@"加載失敗");
}
return image;
}
@end