runtime是運行時機制粗梭,Objective-C是面向運行時的語言,就是說它會盡可能的把編譯和鏈接時要執(zhí)行的邏輯延遲到運行時级零。這就給了我們很大的靈活性断医。可以按照需要把消息重定向給合適的對象,甚至可以交換方法的實現(xiàn)等等孩锡。
下面從以下幾個方面來探索
1酷宵、消息機制
2、方法調(diào)用流程
3躬窜、使用runtime交換方法實現(xiàn)
4浇垦、動態(tài)添加方法
5、動態(tài)添加屬性
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++文件
通過查看該文件男韧,我們可以發(fā)現(xiàn),編譯器將其對象創(chuàng)建的兩行代碼編譯為
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經(jīng)過編譯后是使用消息機制來調(diào)用方法的此虑。所以我們可以用發(fā)送消息的方式來調(diào)用方法
id objc = objc_msgSend([NSObject class], @selector(alloc));
objc = objc_msgSend(objc, @selector(init));
那么我們開發(fā)中什么時候需要用到消息機制呢?當我們想調(diào)用私有方法時口锭,可以用runtime朦前。
下面來看一下帶參數(shù)的方法怎么用runtime調(diào)用
objc_msgSend(<#id _Nullable self#>, <#SEL _Nonnull op, ...#>)
從代碼提示可以發(fā)現(xiàn)第三個參數(shù)的...代表可變參數(shù),意味著可以傳入任意個數(shù)的參數(shù)鹃操。
person *p = objc_msgSend([person class], @selector(alloc));
p = objc_msgSend(p, @selector(init));
objc_msgSend(p, @selector(eat:),@"蘋果");
我定義了一個person類韭寸,并且未在.h文件中暴露方法聲明,從運行結(jié)果可以看出荆隘,使用runtime可以實現(xiàn)對私有方法的調(diào)用恩伺。
2、方法調(diào)用流程椰拒。
方法分為對象方法和類方法晶渠。對象方法保存在方法列表中,類方法保存在元類的方法列表中燃观。
1)褒脯、通過isa去對應(yīng)類中查找。
2)缆毁、注冊方法編號
3)番川、根據(jù)方法編號去查找對應(yīng)方法。
4)积锅、找到的只是最終函數(shù)實現(xiàn)地址爽彤,根據(jù)地址去方法區(qū)調(diào)用方法 。
3缚陷、使用runtime交換方法實現(xiàn)
下面我們來看這樣一個場景适篙,當用UIImage加載圖片時,如果需要判斷圖片是否加載成功箫爷,就需要增加一個判斷嚷节,代碼如下:
UIImage *img = [UIImage imageNamed:@"1.png"];
if (img == nil) {
NSLog(@"圖片加載失敗");
}else{
NSLog(@"圖片加載成功");
}
若需要多次判斷聂儒,就需要每次都進行判斷,無疑會增加代碼的冗余度硫痰。此時可能會想到給UIImage類增加一個分類衩婚,重寫imageNamed方法,在重寫的方法中進行判斷效斑,每次需要判斷時直接調(diào)用分類重寫的方法非春,但是如果是之前代碼中已經(jīng)調(diào)用了很多次原來的方法,再進行修改就會帶來很大的工作量缓屠,此時我們可以想到runtime奇昙,因為它將方法的調(diào)用推遲到了運行時,我們可以在編譯時對自己重寫的方法和類原有的方法進行調(diào)換敌完,以達到上述目的储耐。
進行方法調(diào)換需要在類加載進內(nèi)存的時候交換,并且只能交換一次滨溉,所以可以在分類的+(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、動態(tài)添加方法
為什么要動態(tài)添加方法晦攒?OC都是懶加載機制闽撤,只要一個方法實現(xiàn)了,就會馬上添加到方法列表中勤家。
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
{
//不帶參數(shù)
if (sel == NSSelectorFromString(@"eat")) {
class_addMethod(self,sel,(IMP)aaa,"v@:");
return YES;
}
//帶參數(shù)
if (sel == NSSelectorFromString(@"run:")) {
class_addMethod(self, sel, (IMP)run, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
如上腹尖,當調(diào)用一個對象未實現(xiàn)的方法時柳恐,會調(diào)用該方法內(nèi)部的+(BOOL)resolveInstanceMethod:(SEL)sel方法伐脖,在這個方法內(nèi),我們就可對該對象動態(tài)添加方法乐设。
下面對class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
方法中的參數(shù)進行解釋:
cls:給哪個類添加方法
SEL:添加哪個方法
IMP:方法的實現(xiàn)(函數(shù)入口讼庇,即函數(shù)名)
type:方法類型
5、動態(tài)添加屬性
什么時候需要動態(tài)添加屬性近尚?
本質(zhì):讓某個屬性與對象產(chǎn)生關(guān)聯(lián)蠕啄。
場景:讓NSObject保存string
方法一、給NSObject添加分類
NSObject+cate1.h
@interface NSObject (cate1)
//@property分類: 只會生成get戈锻,set方法聲明歼跟,不會產(chǎn)生實現(xiàn)。也不會產(chǎn)生下劃線成員屬性
@property NSString *name;
@end
NSObject+cate1.m
@implementation NSObject (cate1)
static NSString *_name;
- (void)setName:(NSString *)name
{
_name = name;
}
-(NSString *)name{
return _name;
}
@end
弊端:使用全局靜態(tài)變量格遭,當這個類銷毀時哈街,靜態(tài)變量可能還存在
方法二: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方法聲明拒迅,不會產(chǎn)生實現(xiàn)骚秦。也不會產(chǎn)生下劃線成員屬性
@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