runtime是運行時機制,Objective-C是面向運行時的語言,就是說它會盡可能的把編譯和鏈接時要執行的邏輯延遲到運行時。這就給了我們很大的靈活性??梢园凑招枰严⒅囟ㄏ蚪o合適的對象,甚至可以交換方法的實現等等。
下面從以下幾個方面來探索
1、消息機制
2、方法調用流程
3、使用runtime交換方法實現
4、動態添加方法
5、動態添加屬性
1、消息機制
我們先新建一個命令行項目
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
id objc = [NSObject alloc];
objc = [objc init];
}
return 0;
}
可以使用命令 clang -rewrite-objc- main.m 將其編譯為C++文件
通過查看該文件,我們可以發現,編譯器將其對象創建的兩行代碼編譯為
id objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
objc = ((id (*)(id, SEL))(void *)objc_msgSend)((id)objc, sel_registerName("init"));
對其不必要的代碼進行刪除后可得到
objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"));
objc = objc_msgSend(objc, sel_registerName("init"));
從這兩行代碼可明顯看出,Objective-C經過編譯后是使用消息機制來調用方法的。所以我們可以用發送消息的方式來調用方法
id objc = objc_msgSend([NSObject class], @selector(alloc));
objc = objc_msgSend(objc, @selector(init));
那么我們開發中什么時候需要用到消息機制呢?當我們想調用私有方法時,可以用runtime。
下面來看一下帶參數的方法怎么用runtime調用
objc_msgSend(<#id _Nullable self#>, <#SEL _Nonnull op, ...#>)
從代碼提示可以發現第三個參數的...代表可變參數,意味著可以傳入任意個數的參數。
person *p = objc_msgSend([person class], @selector(alloc));
p = objc_msgSend(p, @selector(init));
objc_msgSend(p, @selector(eat:),@"蘋果");
我定義了一個person類,并且未在.h文件中暴露方法聲明,從運行結果可以看出,使用runtime可以實現對私有方法的調用。
2、方法調用流程。
方法分為對象方法和類方法。對象方法保存在方法列表中,類方法保存在元類的方法列表中。
1)、通過isa去對應類中查找。
2)、注冊方法編號
3)、根據方法編號去查找對應方法。
4)、找到的只是最終函數實現地址,根據地址去方法區調用方法 。
3、使用runtime交換方法實現
下面我們來看這樣一個場景,當用UIImage加載圖片時,如果需要判斷圖片是否加載成功,就需要增加一個判斷,代碼如下:
UIImage *img = [UIImage imageNamed:@"1.png"];
if (img == nil) {
NSLog(@"圖片加載失敗");
}else{
NSLog(@"圖片加載成功");
}
若需要多次判斷,就需要每次都進行判斷,無疑會增加代碼的冗余度。此時可能會想到給UIImage類增加一個分類,重寫imageNamed方法,在重寫的方法中進行判斷,每次需要判斷時直接調用分類重寫的方法,但是如果是之前代碼中已經調用了很多次原來的方法,再進行修改就會帶來很大的工作量,此時我們可以想到runtime,因為它將方法的調用推遲到了運行時,我們可以在編譯時對自己重寫的方法和類原有的方法進行調換,以達到上述目的。
進行方法調換需要在類加載進內存的時候交換,并且只能交換一次,所以可以在分類的+(void)load方法中交換。主要代碼如下:
+ (void)load
{
Method imagenamed = class_getClassMethod(self, @selector(imageNamed:));
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
method_exchangeImplementations(imagenamed, imageWithName);
}
+(UIImage *)imageWithName:(NSString *)name
{
UIImage *image = [UIImage imageWithName:name];
if (image) {
NSLog(@"加載成功");
}else{
NSLog(@"加載失敗");
}
return image;
}
4、動態添加方法
為什么要動態添加方法?OC都是懶加載機制,只要一個方法實現了,就會馬上添加到方法列表中。
person *p = [[person alloc]init];
[p performSelector:@selector(eat)];
[p performSelector:@selector(run:) withObject:@"操場"];
@implementation person
void aaa(){
NSLog(@"aaaa");
}
void run(id self,SEL _cmd,NSString *arg){
NSLog(@"在%@跑",arg);
}
+(BOOL)resolveInstanceMethod:(SEL)sel
{
//不帶參數
if (sel == NSSelectorFromString(@"eat")) {
class_addMethod(self,sel,(IMP)aaa,"v@:");
return YES;
}
//帶參數
if (sel == NSSelectorFromString(@"run:")) {
class_addMethod(self, sel, (IMP)run, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
如上,當調用一個對象未實現的方法時,會調用該方法內部的+(BOOL)resolveInstanceMethod:(SEL)sel方法,在這個方法內,我們就可對該對象動態添加方法。
下面對class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
方法中的參數進行解釋:
cls:給哪個類添加方法
SEL:添加哪個方法
IMP:方法的實現(函數入口,即函數名)
type:方法類型
5、動態添加屬性
什么時候需要動態添加屬性?
本質:讓某個屬性與對象產生關聯。
場景:讓NSObject保存string
方法一、給NSObject添加分類
NSObject+cate1.h
@interface NSObject (cate1)
//@property分類: 只會生成get,set方法聲明,不會產生實現。也不會產生下劃線成員屬性
@property NSString *name;
@end
NSObject+cate1.m
@implementation NSObject (cate1)
static NSString *_name;
- (void)setName:(NSString *)name
{
_name = name;
}
-(NSString *)name{
return _name;
}
@end
弊端:使用全局靜態變量,當這個類銷毀時,靜態變量可能還存在
方法二:runtime
objc_setAssociatedObject(<#id _Nonnull object#>, <#const void * _Nonnull key#>, <#id _Nullable value#>, <#objc_AssociationPolicy policy#>);
object:給哪個對象添加屬性
key:屬性名稱
value:屬性值
policy:保存策略
.h
@interface NSObject (cate1)
//@property分類: 只會生成get,set方法聲明,不會產生實現。也不會產生下劃線成員屬性
@property NSString *name;
@end
.m
@implementation NSObject (cate1)
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-(NSString *)name{
return objc_getAssociatedObject(self, @"name");
}
@end