ios runtime總結(一)

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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。