RunTime

runtime是什么?
  • runtime又稱運行時,也就是運行時候的一些機制,其中最總要的就是消息機制.
  • 任何調用方法的本質都是發送消息

oc 與c 有什么不同?
  • c語言函數,在編譯階段就確定調用哪個函數
  • oc方法,在編譯的時候并不能決定調用哪個方法,只有在真正運行的時候才會根據方法名稱找到與之對應的函數實現
    *注意:對于oc 來說是調用方法,對于c來說是調用函數.方法和函數是有區別的,對于oc根據方法名去找到函數名,相當于函數實現是方法的實現.函數名是函數實現的入口
  • 在編譯階段,oc 中的方法只要聲明就不會報錯,而c中的函數必須實現,才不會報錯.

runtime發消息
NSObject *p = [NSObject alloc];
p = [p init];

clang -rewrite-objc ViewController.m
如果報錯看這里http://www.lxweimin.com/p/43a09727eb2c
這句話將ViewController.m代買轉化成ViewController.cpp,oc代碼轉成c++代碼
然后我們去ViewController.cpp看看6萬多行代碼,
搜索@implementation ViewController

 NSObject *p = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
 p = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("init"));

精簡一下:

 NSObject *p = objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"));
 p = objc_msgSend(p, sel_registerName("init"));

從上面代碼我們看到,我們創建對象的本質其實就是發消息
objc_msgSend:發消息
objc_getClass : 獲取類
sel_registerName:注冊方法編號,相當于@selector()


runtime幾種用途
  • 方法交換

舉個例子,比如[UIImage imageNamed:@"1"],那么這張圖片有沒有也不知道,那么我們該怎么做呢?

  1. 可以建一個自定義的類,每次調用自己的類,但是這樣每次都需要導入頭文件,而且如果項目已經維護了好久,那么這種方法會改起來沒麻煩
  2. 可以寫個類別,但是問題同上

因此,這時候我們需要想到runtime方法交換,因為我們不想改動之前的代碼,但是之前的代碼不符合我們需求,我們需要擴展,所以我們應該想到這種方法.
那么需要將imageNamed系統方法實現IMP 和 自定義的 gl_ imageNamedIMP 交換

 //新建一個類別
//值調用一次,在加載類的時候調用
+ (void)load{
    
    Method method1 = class_getClassMethod([self class], @selector(imageNamed:));
    Method method2 = class_getClassMethod([self class], @selector(gl_imageNamed:));

    
    method_exchangeImplementations(method1, method2);
    
}

//會調用多次,可以用單利來限制代碼執行次數,因為swift里沒有+load,所以只能用+initialize
+ (void)initialize{
    
}

+ (UIImage *)gl_imageNamed:(NSString *)name{

    UIImage *image = [self gl_imageNamed:name];
    
    if (image) {
        NSLog(@"圖片存在");
    } else {
        NSLog(@"圖片不存在");
    }
    
    return image;
}

其中我說明了load方法和initialize 的區別
這樣我在調用UIImage *image = [UIImage imageNamed:@"qq"];會告訴我圖片沒有


  • 動態添加方法

1.方法調用流程:
1)通過isa 指針去對應的類中去找方法,對象方法去類對象的方法列表去找方法,類方法去元類的方法列表中去找方法.
2)注冊方法編號
3)根據方法編號去查找方法
4)找到最終函數實現地址

2.運行時添加一個方法,我再舉個??:
我建一個Person類,接著調用

Person *p = [[Person alloc] init];
[p performSelector:@selector(eat)];

為什么用performSelector,因為對于沒有聲明的方法,編譯時無法編過去, performSelector是運行時執行,接著運行一下,crash!
reason: '-[Person eat]: unrecognized selector sent to instance 0x604000002220'
我們可以攔截這個崩潰,在Person.m里添加

void eat(id self,SEL _cmd){
    NSLog(@"吃了");
}

//第一次攔截,其實有三次轉發
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
        class_addMethod([self class], sel, (IMP)eat, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

class_addMethod 方法最后一參數在蘋果文檔中查找
v:代表返回值是void
@:代表參數是id
::代表參數是 sel
void eat(id self,SEL _cmd){ NSLog(@"吃了"); }:該函數的兩個參數,是默認傳的,其實蘋果每個函數調用都會傳self和_cmd,只是編譯器幫我們做了.

如果我們使用[p performSelector:@selector(eat) withObject:@1];帶參數的,對應的寫成下面這個樣子的

void eat(id self,SEL _cmd,NSNumber *n){
    NSLog(@"吃了%@",n);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
        class_addMethod([self class], sel, (IMP)eat, "v@:@");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

  • 動態添加屬性

本質也就是類別中添加一個屬性,
因為類別中只會set get 方法聲明,不會實現,也不會生成對應的下劃線成員變量.看看代碼

@interface Person : NSObject

@property NSString *name;

@end

由于方法沒有實現,所以給屬性設置策略是無用的

- (void)setName:(NSString *)name{
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name{
    return  objc_getAssociatedObject(self, @"name");
}

將該屬性關聯到Person對象上


  • 字典轉模型

重中之重!
好多解析model 的框架底層原理都是基于runtime

1.動態添加屬性代碼

{
    "classNum": "3年二班",
    "user": [
             {"name":"張三"
             "age":22
             },
             {"name":"李四"
             "age":34
             },
             ]
    "isHave":YES
    "totol":2
}

建一個NSDictionary 分類

- (void)createPropertyWithDict{
    NSMutableString *string = [NSMutableString string];

    [self enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        
        
        if ([obj isKindOfClass:[NSString class]]) {
            NSString* str = [NSString stringWithFormat:@"@property (nonatomic,strong) NSString *%@;",key];
            [string appendFormat:@"\n%@\n",str];
        }else if ([obj isKindOfClass:[NSArray class]]){
            NSString* str = [NSString stringWithFormat:@"@property (nonatomic,strong) NSArray *%@;",key];
            [string appendFormat:@"\n%@\n",str];

        }else if ([obj isKindOfClass:[NSDictionary class]]){
            NSString* str = [NSString stringWithFormat:@"@property (nonatomic,strong) NSDictionary *%@;",key];
            [string appendFormat:@"\n%@\n",str];

        }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
            NSString* str = [NSString stringWithFormat:@"@property (nonatomic,assign) BOOL %@;",key];
            [string appendFormat:@"\n%@\n",str];

        }else if ([obj isKindOfClass:[NSNumber class]]){
            NSString* str = [NSString stringWithFormat:@"@property (nonatomic,assign) NSInteger %@;",key];
            [string appendFormat:@"\n%@\n",str];
        }

    }];
    
    NSLog(@"%@",string);

}

然后用字典直接調用createPropertyWithDict
得到結果如下

@property (nonatomic,assign) NSInteger totole;

@property (nonatomic,strong) NSString * classNum;

@property (nonatomic,assign) BOOL isHave;

@property (nonatomic,strong) NSArray *user;

2.字典轉model

- (id)modelWithDict:(NSDictionary*)dict{
    NSObject *obj = [[[self class] alloc] init];
    unsigned int count = 0;
    //之所以用class_copyIvarList 而不用 class_copyPropertyList,因為我們可能忽略成員變量,但不會忽略屬性
    Ivar *IvalList = class_copyIvarList([self class], &count);
    for (int i = 0; i<count; i++) {
        Ivar ivar = IvalList[i];
        //ivar_getName(ivar) 得到成員變量的名字
        //c語言字符串轉oc字符串轉
        NSString *ivarStr = [NSString stringWithUTF8String:ivar_getName(ivar)];
        ivarStr = [ivarStr substringFromIndex:1];
        //ivar_getTypeEncoding(ivar) 得到成員變量的類型
        NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
        ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];

        [obj setValue:dict[ivarStr] forKey:ivarStr];
    }
    
    return obj;
}

以上就是關于runtime 的一個總結,很多細節部分需要自己去動手才能發現,謝謝

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

推薦閱讀更多精彩內容

  • iOS開發-- Runtime的幾個小例子 字數2756閱讀1867評論22喜歡88 一、什么是runtime(也...
    K_Gopher閱讀 372評論 0 0
  • 對于從事 iOS 開發人員來說,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,732評論 7 64
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,154評論 0 9
  • Runtime是什么 Runtime 又叫運行時,是一套底層的 C 語言 API,其為 iOS 內部的核心之一,我...
    SuAdrenine閱讀 899評論 0 3
  • 1 同事大姐給同事娜娜介紹個對象,那人我們都認識,也是同事。說實話,還行。長相過得去,無不良嗜好,收入略高于娜娜,...
    哆啦A夢的理想閱讀 479評論 0 5