iOS模式詳解runtime


掘金原文: https://juejin.im/post/593f77085c497d006ba389f0

對你有新思想 或是 習慣,你的 「Star & Fork」 是對我最大的讚賞 !

對於從事 iOS 開發人員來說,所有的人都會答出「runtime 是運行時」,什麼情況下用runtime?,大部分人能說出「給分類動態添加屬性 || 交換方法」,再問一句「runtime 消息機制的調用流程 || 能體現runtime 強大之處的應用場景」,到這,能知道答案的寥寥無幾,很少有人會說到「黑魔法」這三個字,

runtime 是 iOS 編程中比較難的模塊,想要深入學習 OC,那 runtime 是你必須要熟練掌握的東西,下面是我對 runtime 的整理,從零開始,由淺入深,並且帶了幾個 runtime 實際開發的應用場景。

不管誰的博客上面寫的文章(也包括自己),閱讀的你要敢於去驗證,停止無意義的?copy ???? paste。

在「時間 & 知識 」有限內,總結的文章難免有「未全、不足 」的地方,還望各位好友指出,以提高文章質量;@ 白開水ln

2230763-80f08bce98067f27.jpg

目錄:

  • runtime 概念

  • runtime 消息機制

  • runtime 方法調用流程「消息機制」

  • runtime 運行時常見作用

  • runtime 常用開發應用場景「工作掌握」

1.runtime 交換方法

2.runtime 給分類動態添加屬性

3.runtime 字典轉模型(Runtime 考慮三種情況實現)

  • runtime 運行時其它作用「面試熟悉」

1.動態添加方法

2.動態變量控制

3.實現NSCoding的自動歸檔和解檔

4.runtime 下Class的各項操作

5.runtime 幾個參數概念

  • 什麼是 method swizzling(俗稱黑魔法)

  • 最後一道面試題的註解

  • runtime模塊簡友文章推薦(??數量較多)

  • 實戰應用場景(持續)

  • 期待 & 後續 Swift版-runtime面試、工作

本篇文章較長一些,強烈建議先 ?? 收藏,在進行閱讀 !

runtime 概念

Objective-C 是基於 C 的,它爲 C 添加了面向對象的特性。它將很多靜態語言在編譯和鏈接時期做的事放到了 runtime 運行時來處理,可以說 runtime 是我們 Objective-C 幕後工作者。

runtime(簡稱運行時),是一套 純C(C和彙編寫的) 的API。而 OC 就是 運行時機制,也就是在運行時候的一些機制,其中最主要的是 消息機制。

對於 C 語言,函數的調用在編譯的時候會決定調用哪個函數。

OC的函數調用成爲消息發送,屬於 動態調用過程。在編譯的時候並不能決定真正調用哪個函數,只有在真正運行的時候纔會根據函數的名稱找到對應的函數來調用。

事實證明:在編譯階段,OC 可以 調用任何函數,即使這個函數並未實現,只要聲明過就不會報錯,只有當運行的時候纔會報錯,這是因爲OC是運行時動態調用的。而 C 語言 調用未實現的函數 就會報錯。

runtime 消息機制

我們寫 OC 代碼,它在運行的時候也是轉換成了 runtime 方式運行的。任何方法調用本質:就是發送一個消息(用 runtime發送消息,OC 底層實現通過 runtime 實現)。

消息機制原理:對象根據方法編號SEL去映射表查找對應的方法實現。

每一個 OC 的方法,底層必然有一個與之對應的 runtime 方法。

2230763-909d38119a9387f1.png

OC-->runtime

簡單示例:

驗證:方法調用,是否真的是轉換爲消息機制?

  • 必須要導入頭文件 #import

註解1:我們導入系統的頭文件,一般用尖括號。

註解2:OC 解決消息機制方法提示步驟【查找build setting -> 搜索msg -> objc_msgSend(YES --> NO)】

註解3:最終生成消息機制,編譯器做的事情,最終代碼,需要把當前代碼重新編譯,用xcode編譯器,【clang -rewrite-objc main.m 查看最終生成代碼】,示例:cd main.m --> 輸入前面指令,就會生成 .opp文件(C++代碼)

註解4:這裏一般不會直接導入

2230763-95d6b7bddc1fe4e8.png

message.h

  • 示例代碼:OC 方法-->runtime 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
說明: eat(無參) 和 run(有參) 是 Person模型類中的私有方法「可以幫我調用私有方法」;
// Person *p = [Person alloc];
// 底層的實際寫法
Person *p = objc_msgSend(objc_getClass( "Person" ), sel_registerName( "alloc" ));
 
// p = [p init];
p = objc_msgSend(p, sel_registerName( "init" ));
 
// 調用對象方法(本質:讓對象發送消息)
//[p eat];
 
// 本質:讓類對象發送消息
objc_msgSend(p, @selector(eat));
objc_msgSend([Person class], @selector(run:),20);
 
//---------------------------  ------------------------------//
// 也許下面這種好理解一點
 
// id objc = [NSObject alloc];
id objc = objc_msgSend([NSObject class], @selector(alloc));
 
// objc = [objc init];
objc = objc_msgSend(objc, @selector(init));

runtime 方法調用流程「消息機制」

面試:消息機制方法調用流程

  • 怎麼去調用eat方法,對象方法:(保存到類對象的方法列表) ,類方法:(保存到元類(Meta Class)中方法列表)。

1.OC 在向一個對象發送消息時,runtime 庫會根據對象的 isa指針找到該對象對應的類或其父類中查找方法。。

2.註冊方法編號(這裏用方法編號的好處,可以快速查找)。

3.根據方法編號去查找對應方法。

4.找到只是最終函數實現地址,根據地址去方法區調用對應函數。

  • 補充:一個objc 對象的 isa 的指針指向什麼?有什麼作用?

每一個對象內部都有一個isa指針,這個指針是指向它的真實類型,根據這個指針就能知道將來調用哪個類的方法。

runtime 常見作用

  • 動態交換兩個方法的實現

  • 動態添加屬性

  • 實現字典轉模型的自動轉換

  • 發送消息

  • 動態添加方法

  • 攔截並替換方法

  • 實現 NSCoding 的自動歸檔和解檔

runtime 常用開發應用場景「工作掌握」

runtime 交換方法

應用場景:當第三方框架 或者 系統原生方法功能不能滿足我們的時候,我們可以在保持系統原有方法功能的基礎上,添加額外的功能。

需求:加載一張圖片直接用[UIImage imageNamed:@"image"];是無法知道到底有沒有加載成功。給系統的imageNamed添加額外功能(是否加載圖片成功)。

方案一:繼承系統的類,重寫方法.(弊端:每次使用都需要導入)

方案二:使用 runtime,交換方法.

實現步驟

  • 1.給系統的方法添加分類

  • 2.自己實現一個帶有擴展功能的方法

  • 3.交換方法,只需要交換一次,

案例代碼:方法+調用+打印輸出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (void)viewDidLoad {
     [ super  viewDidLoad];
     // 方案一:先搞個分類,定義一個能加載圖片並且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
     // 方案二:交換 imageNamed 和 ln_imageNamed 的實現,就能調用 imageNamed,間接調用 ln_imageNamed 的實現。
     UIImage *image = [UIImage imageNamed:@ "123" ];
}
 
#import
@implementation UIImage (Image)
/**
  load方法: 把類加載進內存的時候調用,只會調用一次
  方法應先交換,再去調用
  */
+ (void)load {
 
     // 1.獲取 imageNamed方法地址
     // class_getClassMethod(獲取某個類的方法)
     Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
     // 2.獲取 ln_imageNamed方法地址
     Method ln_imageNamedMethod = class_getClassMethod(self, @selector(ln_imageNamed:));
 
     // 3.交換方法地址,相當於交換實現方式;「method_exchangeImplementations 交換兩個方法的實現」
     method_exchangeImplementations(imageNamedMethod, ln_imageNamedMethod);
}
 
/**
  看清楚下面是不會有死循環的
  調用 imageNamed => ln_imageNamed
  調用 ln_imageNamed => imageNamed
  */
// 加載圖片 且 帶判斷是否加載成功
+ (UIImage *)ln_imageNamed:(NSString *)name {
 
     UIImage *image = [UIImage ln_imageNamed:name];
     if  (image) {
         NSLog(@ "runtime添加額外功能--加載成功" );
     else  {
         NSLog(@ "runtime添加額外功能--加載失敗" );
     }
     return  image;
}
 
/**
  不能在分類中重寫系統方法imageNamed,因爲會把系統的功能給覆蓋掉,而且分類中不能調用super
  所以第二步,我們要 自己實現一個帶有擴展功能的方法.
  + (UIImage *)imageNamed:(NSString *)name {
 
  }
  */
@end
 
 
// 打印輸出
2017-02-17 17:52:14.693 runtime[12761:543574] runtime添加額外功能--加載成功

總結:我們所做的就是在方法調用流程第三步的時候,交換兩個方法地址指向。而且我們改變指向要在系統的imageNamed:方法調用前,所以將代碼寫在了分類的load方法裏。最後當運行的時候系統的方法就會去找我們的方法的實現。

runtime 給分類動態添加屬性

原理:給一個類聲明屬性,其實本質就是給這個類添加關聯,並不是直接把這個值的內存空間添加到類存空間。

應用場景:給系統的類添加屬性的時候,可以使用runtime動態添加屬性方法。

註解:系統 NSObject 添加一個分類,我們知道在分類中是不能夠添加成員屬性的,雖然我們用了@property,但是僅僅會自動生成get和set方法的聲明,並沒有帶下劃線的屬性和方法實現生成。但是我們可以通過runtime就可以做到給它方法的實現。

需求:給系統 NSObject 類動態添加屬性 name 字符串。

案例代碼:方法+調用+打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@interface NSObject (Property)
 
// @property分類:只會生成get,set方法聲明,不會生成實現,也不會生成下劃線成員屬性
@property NSString *name;
@property NSString *height;
@end
 
@implementation NSObject (Property)
 
- (void)setName:(NSString *)name {
 
     // objc_setAssociatedObject(將某個值跟某個對象關聯起來,將某個值存儲到某個對象中)
     // object:給哪個對象添加屬性
     // key:屬性名稱
     // value:屬性值
     // policy:保存策略
     objc_setAssociatedObject(self, @ "name" , name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
- (NSString *)name {
     return  objc_getAssociatedObject(self, @ "name" );
}
 
// 調用
NSObject *objc = [[NSObject alloc] init];
objc.name = @ "123" ;
NSLog(@ "runtime動態添加屬性name==%@" ,objc.name);
 
// 打印輸出
2017-02-17 19:37:10.530 runtime[12761:543574] runtime動態添加屬性--name == 123

總結:其實,給屬性賦值的本質,就是讓屬性與一個對象產生關聯,所以要給NSObject的分類的name屬性賦值就是讓name和NSObject產生關聯,而runtime可以做到這一點。

runtime 字典轉模型

字典轉模型的方式:

  • 一個一個的給模型屬性賦值(初學者)。

  • 字典轉模型KVC實現

        KVC 字典轉模型弊端:必須保證,模型中的屬性和字典中的key 一一對應。

        如果不一致,就會調用[setValue:forUndefinedKey:] 報key找不到的錯。

        分析:模型中的屬性和字典的key不一一對應,系統就會調用setValue:forUndefinedKey:報錯。

        解決:重寫對象的setValue:forUndefinedKey:,把系統的方法覆蓋,就能繼續使用KVC,字典轉模型了。

  • 字典轉模型 Runtime 實現

思路:利用運行時,遍歷模型中所有屬性,根據模型的屬性名,去字典中查找key,取出對應的值,給模型的屬性賦值(從提醒:字典中取值,不一定要全部取出來)。

考慮情況

1.當字典的key和模型的屬性匹配不上。

2.模型中嵌套模型(模型屬性是另外一個模型對象)。

3.數組中裝着模型(模型的屬性是一個數組,數組中是一個個模型對象)。

註解:根據上面的三種特殊情況,先是字典的key和模型的屬性不對應的情況。不對應有兩種,一種是字典的鍵值大於模型屬性數量,這時候我們不需要任何處理,因爲runtime是先遍歷模型所有屬性,再去字典中根據屬性名找對應值進行賦值,多餘的鍵值對也當然不會去看了;另外一種是模型屬性數量大於字典的鍵值對,這時候由於屬性沒有對應值會被賦值爲nil,就會導致crash,我們只需加一個判斷即可。考慮三種情況下面一一註解;

步驟:提供一個NSObject分類,專門字典轉模型,以後所有模型都可以通過這個分類實現字典轉模型。

MJExtension 字典轉模型實現

底層也是對 runtime 的封裝,纔可以把一個模型中所有屬性遍歷出來。(你之所以看不懂,是 MJ 封裝了很多層而已^_^.)。

這裏針對字典轉模型 KVC 實現,就不做詳解了,如果你 對 KVC 詳解使用或是實現原理 不是很清楚的,可以參考 實用「KVC編碼 & KVO監聽

字典轉模型 Runtime 方式實現:

說明:下面這個示例,是考慮三種情況包含在內的轉換示例,具體可以看圖上的註解

2230763-3784d32a60631433.png

Runtime 字典轉模型

1、runtime 字典轉模型-->字典的 key 和模型的屬性不匹配「模型屬性數量大於字典鍵值對數」,這種情況處理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Runtime:根據模型中屬性,去字典中取出對應的value給模型屬性賦值
// 思路:遍歷模型中所有屬性->使用運行時
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
     // 1.創建對應的對象
     id objc = [[self alloc] init];
 
     // 2.利用runtime給對象中的屬性賦值
     /**
      class_copyIvarList: 獲取類中的所有成員變量
      Ivar:成員變量
      第一個參數:表示獲取哪個類中的成員變量
      第二個參數:表示這個類有多少成員變量,傳入一個Int變量地址,會自動給這個變量賦值
      返回值Ivar *:指的是一個ivar數組,會把所有成員屬性放在一個數組中,通過返回的數組就能全部獲取到。
      count: 成員變量個數
      */
     unsigned int count = 0;
     // 獲取類中的所有成員變量
     Ivar *ivarList = class_copyIvarList(self, &count);
 
     // 遍歷所有成員變量
     for  (int i = 0; i < count; i++) {
         // 根據角標,從數組取出對應的成員變量
         Ivar ivar = ivarList[i];
 
         // 獲取成員變量名字
         NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
 
         // 處理成員變量名->字典中的key(去掉 _ ,從第一個角標開始截取)
         NSString *key = [ivarName substringFromIndex:1];
 
         // 根據成員屬性名去字典中查找對應的value
         id value = dict[key];
 
         // 【如果模型屬性數量大於字典鍵值對數理,模型屬性會被賦值爲nil】
         // 而報錯 (could not set nil as the value for the key age.)
         if  (value) {
             // 給模型中屬性賦值
             [objc setValue:value forKey:key];
         }
 
     }
 
     return  objc;
}

:這裏在獲取模型類中的所有屬性名,是採取 class_copyIvarList 先獲取成員變量(以下劃線開頭) ,然後再處理成員變量名->字典中的key(去掉 _ ,從第一個角標開始截取) 得到屬性名。

原因

  • Ivar:成員變量,以下劃線開頭,Property 屬性

  • 獲取類裏面屬性 class_copyPropertyList

  • 獲取類中的所有成員變量 class_copyIvarList

1
2
3
4
5
{
     int _a;  // 成員變量
}
 
@property (nonatomic, assign) NSInteger attitudes_count;  // 屬性

這裏有成員變量,就不會漏掉屬性;如果有屬性,可能會漏掉成員變量;

使用runtime字典轉模型獲取模型屬性名的時候,最好獲取成員屬性名Ivar因爲可能會有個屬性是沒有setter和``getter方法的。

2、runtime 字典轉模型-->模型中嵌套模型「模型屬性是另外一個模型對象」,這種情況處理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
+ (instancetype)modelWithDict2:(NSDictionary *)dict
{
     // 1.創建對應的對象
     id objc = [[self alloc] init];
 
     // 2.利用runtime給對象中的屬性賦值
     unsigned int count = 0;
     // 獲取類中的所有成員變量
     Ivar *ivarList = class_copyIvarList(self, &count);
 
     // 遍歷所有成員變量
     for  (int i = 0; i < count; i++) {
         // 根據角標,從數組取出對應的成員變量
         Ivar ivar = ivarList[i];
 
         // 獲取成員變量名字
         NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
         // 獲取成員變量類型
         NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
 
         // 替換: @\"User\" -> User
         ivarType = [ivarType stringByReplacingOccurrencesOfString:@ "\""  withString:@ "" ];
         ivarType = [ivarType stringByReplacingOccurrencesOfString:@ "@"  withString:@ "" ];
 
         // 處理成員屬性名->字典中的key(去掉 _ ,從第一個角標開始截取)
         NSString *key = [ivarName substringFromIndex:1];
 
         // 根據成員屬性名去字典中查找對應的value
         id value = dict[key];
 
         //---------------------------  ------------------------------//
         //
         // 二級轉換:如果字典中還有字典,也需要把對應的字典轉換成模型
         // 判斷下value是否是字典,並且是自定義對象才需要轉換
         if  ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@ "NS" ]) {
 
             // 字典轉換成模型 userDict => User模型, 轉換成哪個模型
             // 根據字符串類名生成類對象
             Class modelClass = NSClassFromString(ivarType);
 
             if  (modelClass) {  // 有對應的模型才需要轉
                 // 把字典轉模型
                 value = [modelClass modelWithDict2:value];
             }
         }
 
         // 給模型中屬性賦值
         if  (value) {
             [objc setValue:value forKey:key];
         }
     }
     return  objc;
}

3、runtime 字典轉模型-->數組中裝着模型「模型的屬性是一個數組,數組中是字典模型對象」,這種情況處理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// Runtime:根據模型中屬性,去字典中取出對應的value給模型屬性賦值
// 思路:遍歷模型中所有屬性->使用運行時
+ (instancetype)modelWithDict3:(NSDictionary *)dict
{
     // 1.創建對應的對象
     id objc = [[self alloc] init];
 
     // 2.利用runtime給對象中的屬性賦值
     unsigned int count = 0;
     // 獲取類中的所有成員變量
     Ivar *ivarList = class_copyIvarList(self, &count);
 
     // 遍歷所有成員變量
     for  (int i = 0; i < count; i++) {
         // 根據角標,從數組取出對應的成員變量
         Ivar ivar = ivarList[i];
 
         // 獲取成員變量名字
         NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
 
         // 處理成員屬性名->字典中的key(去掉 _ ,從第一個角標開始截取)
         NSString *key = [ivarName substringFromIndex:1];
 
         // 根據成員屬性名去字典中查找對應的value
         id value = dict[key];
 
 
         //---------------------------  ------------------------------//
         //
 
         // 三級轉換:NSArray中也是字典,把數組中的字典轉換成模型.
         // 判斷值是否是數組
         if  ([value isKindOfClass:[NSArray class]]) {
             // 判斷對應類有沒有實現字典數組轉模型數組的協議
             // arrayContainModelClass 提供一個協議,只要遵守這個協議的類,都能把數組中的字典轉模型
             if  ([self respondsToSelector:@selector(arrayContainModelClass)]) {
 
                 // 轉換成id類型,就能調用任何對象的方法
                 id idSelf = self;
 
                 // 獲取數組中字典對應的模型
                 NSString *type =  [idSelf arrayContainModelClass][key];
 
                 // 生成模型
                 Class classModel = NSClassFromString(type);
                 NSMutableArray *arrM = [NSMutableArray array];
                 // 遍歷字典數組,生成模型數組
                 for  (NSDictionary *dict  in  value) {
                     // 字典轉模型
                     id model =  [classModel modelWithDict3:dict];
                     [arrM addObject:model];
                 }
 
                 // 把模型數組賦值給value
                 value = arrM;
 
             }
         }
 
         // 如果模型屬性數量大於字典鍵值對數理,模型屬性會被賦值爲nil,而報錯
         if  (value) {
             // 給模型中屬性賦值
             [objc setValue:value forKey:key];
         }
     }
     return  objc;
}

2230763-d163d8ac0c736b7e.png

runtime字典轉模型-->數組中裝着模型 打印輸出

總結:我們既然能獲取到屬性類型,那就可以攔截到模型的那個數組屬性,進而對數組中每個模型遍歷並字典轉模型,但是我們不知道數組中的模型都是什麼類型,我們可以聲明一個方法,該方法目的不是讓其調用,而是讓其實現並返回模型的類型。

本文爲「簡書-白開水ln」作者原創;我的寫作,希望能簡化到初學者儘快入門和老司機繁瑣回顧 ^_^.

這裏提到的你如果不是很清楚,建議參考我的Demo,重要的部分代碼中都有相應的註解和文字打印,運行程序可以很直觀的表現。

runtime 其它作用「面試熟悉」

動態添加方法

應用場景:如果一個類方法非常多,加載類到內存的時候也比較耗費資源,需要給每個方法生成映射表,可以使用動態給某個類,添加方法解決。

註解:OC 中我們很習慣的會用懶加載,當用到的時候纔去加載它,但是實際上只要一個類實現了某個方法,就會被加載進內存。當我們不想加載這麼多方法的時候,就會使用到 runtime 動態的添加方法。

需求:runtime 動態添加方法處理調用一個未實現的方法 和 去除報錯。

案例代碼:方法+調用+打印輸出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (void)viewDidLoad {
     [ super  viewDidLoad];   
     Person *p = [[Person alloc] init];
     // 默認person,沒有實現run:方法,可以通過performSelector調用,但是會報錯。
     // 動態添加方法就不會報錯
     [p performSelector:@selector(run:) withObject:@10];
}
 
@implementation Person
// 沒有返回值,1個參數
// void,(id,SEL)
void aaa(id self, SEL _cmd, NSNumber *meter) {
     NSLog(@ "跑了%@米" , meter);
}
 
// 任何方法默認都有兩個隱式參數,self,_cmd(當前方法的方法編號)
// 什麼時候調用:只要一個對象調用了一個未實現的方法就會調用這個方法,進行處理
// 作用:動態添加方法,處理未實現
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
     // [NSStringFromSelector(sel) isEqualToString:@"run"];
     if  (sel == NSSelectorFromString(@ "run:" )) {
         // 動態添加run方法
         // class: 給哪個類添加方法
         // SEL: 添加哪個方法,即添加方法的方法編號
         // IMP: 方法實現 => 函數 => 函數入口 => 函數名(添加方法的函數實現(函數地址))
         // type: 方法類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd
         class_addMethod(self, sel, (IMP)aaa,  "[email protected]:@" );
         return  YES;
     }
     return  [ super  resolveInstanceMethod:sel];
}
@end
 
// 打印輸出
2017-02-17 19:05:03.917 runtime[12761:543574] runtime動態添加方法--跑了10米

動態變量控制

現在有一個Person類,創建 xiaoming對象

  • 動態獲取 XiaoMing 類中的所有屬性 [當然包括私有]

1
Ivar *ivar = class_copyIvarList([self.xiaoming class], &count);
  • 遍歷屬性找到對應name字段

1
const char *varName = ivar_getName( var );
  • 修改對應的字段值成20

1
object_setIvar(self.xiaoMing,  var , @ "20" );
  • 代碼參考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
-(void)answer{
    unsigned int count = 0;
    Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count);
    for  (int i = 0; i<count; i++) {        ivar  var  = " ivar[i];"         const char *varname = " ivar_getName(var);"         nsstring *name = " [NSString stringWithUTF8String:varName];"         if  ([name isequaltostring:@ "_age" ]) {= ""             object_setivar(self.xiaoming,  var , @ "20" );= ""             break ;= ""         }= ""     }= ""     nslog(@ "xiaoming's age is %@" ,self.xiaoming.age);= "" }<= "" pre= "" ><p><strong><span style= "font-size: 24px;" >實現NSCoding的自動歸檔和解檔</span></strong></p><p>如果你實現過自定義模型數據持久化的過程,那麼你也肯定明白,如果一個模型有許多個屬性,那麼我們需要對每個屬性都實現一遍encodeObject 和 decodeObjectForKey方法,如果這樣的模型又有很多個,這還真的是一個十分麻煩的事情。下面來看看簡單的實現方式。</p><p>假設現在有一個Movie類,有3個屬性。先看下<strong> .h</strong>文件</p><p><span style= "text-decoration: none;" ></span></p><pre class= "brush:js;toolbar:false" > // Movie.h文件
//1. 如果想要當前類可以實現歸檔與反歸檔,需要遵守一個協議NSCoding
@interface Movie : [email protected] (nonatomic, copy) NSString *movieId;
@property (nonatomic, copy) NSString *movieName;
@property (nonatomic, copy) NSString *pic_url;
@end</pre><p>如果是正常寫法,<strong> .m</strong> 文件應該是這樣的:</p><pre class= "brush:js;toolbar:false" > // Movie.m文件
@implementation Movie
 
- (void)encodeWithCoder:(NSCoder *)aCoder
{
     [aCoder encodeObject:_movieId forKey:@ "id" ];
     [aCoder encodeObject:_movieName forKey:@ "name" ];
     [aCoder encodeObject:_pic_url forKey:@ "url" ];
 
}
 
- (id)initWithCoder:(NSCoder *)aDecoder
{
     if  (self = [ super  init]) {
         self.movieId = [aDecoder decodeObjectForKey:@ "id" ];
         self.movieName = [aDecoder decodeObjectForKey:@ "name" ];
         self.pic_url = [aDecoder decodeObjectForKey:@ "url" ];
     }
     return  self;
}
@end</pre><p>如果這裏有100個屬性,那麼我們也只能把100個屬性都給寫一遍嗎。</p><p>不過你會使用runtime後,這裏就有更簡便的方法,如下。</p><pre class= "brush:js;toolbar:false" > #import "Movie.h"
#import @implementation Movie
 
- (void)encodeWithCoder:(NSCoder *)encoder
 
{
     unsigned int count = 0;
     Ivar *ivars = class_copyIvarList([Movie class], &count);
 
     for  (int i = 0; i<count; i++) {         = ""   取出i位置對應的成員變量= ""          ivar ivar = " ivars[i];"   查看成員變量= ""          const char *name = " ivar_getName(ivar);"   歸檔= ""          nsstring *key = " [NSString stringWithUTF8String:name];"          id value = " [self valueForKey:key];"          [encoder encodeobject:value forkey:key];= ""      }= ""      free(ivars);= "" }= "" - (id)initwithcoder:(nscoder *)decoder= "" {= ""      if  (self = " [super init]) {"          unsigned int count = " 0;"          ivar *ivars = " class_copyIvarList([Movie class], &count);"          for  (int i = " 0; i<count; i++) {"         = ""         nsstring *key = " [NSString stringWithUTF8String:name];"        id value = " [decoder decodeObjectForKey:key];"   設置到成員變量身上= ""          [self setvalue:value forkey:key];= ""          }= ""          free(ivars);= ""      } = ""      return  self;= "" @end<= "" pre= "" ><p>這樣的方式實現,不管有多少個屬性,寫這幾行代碼就搞定了。怎麼,代碼有點多,</p><p>好說下面看看更加簡便的方法:兩句代碼搞定。</p><pre class= "brush:js;toolbar:false" > #import "Movie.h"
#import #define encodeRuntime(A) \
\
unsigned int count = 0;\
Ivar *ivars = class_copyIvarList([A class], &count);\
for  (int i = 0; i<count; i++) {\ ivar ivar = " ivars[i];\" const char *name ="  ivar_getName(ivar);\ " nsstring *key ="  [NSString stringWithUTF8String:name];\ " id value ="  [self valueForKey:key];\ " [encoder encodeobject:value forkey:key];\=" " }\=" " free(ivars);\=" " \=" " #define initcoderruntime(a) \=" " if (self ="  [ super  init]) {\ " unsigned int count ="  0;\ " ivar *ivars ="  class_copyIvarList([A class], &count);\ " for (int i ="  0; i<count; i++) {\ " [self setvalue:value forkey:key];\=" " return self;\=" " @implementation movie=" " - (void)encodewithcoder:(nscoder *)encoder=" " {=" "     encoderuntime(movie)=" " }=" " - (id)initwithcoder:(nscoder *)decoder=" "     initcoderruntime(movie)=" " @end<=" " pre=" "><p><strong>優化</strong>:上面是encodeWithCoder 和 initWithCoder這兩個方法抽成宏。我們可以把這兩個宏單獨放到一個文件裏面,這裏以後需要進行數據持久化的模型都可以直接使用這兩個宏。</p><p><span style=" font-size: 24px; "><strong>runtime 下Class的各項操作</strong></span></p><p>下面是 runtime 下Class的常見方法 及 帶有使用示例代碼。各項操作,<a href=" http: //www.jianshu.com/p/46dd81402f63" target="_blank">【轉載原著】http://www.jianshu.com/p/46dd81402f63</a></p><p><strong>unsigned int count;</strong></p><ul class=" list-paddingleft-2" style="list-style-type: disc;"><li><p>獲取屬性列表</p></li></ul><pre class="brush:js;toolbar:false">objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for  (unsigned int i=0; i<count; i++) {    const char *propertyname = " property_getName(propertyList[i]);"     nslog(@ "property----=" ">%@" , [NSString stringWithUTF8String:propertyName]);
}</count; i++) {></pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>獲取方法列表</p></li></ul><pre class= "brush:js;toolbar:false" >Method *methodList = class_copyMethodList([self class], &count);
for  (unsigned int i; i<count; i++) {    method method = " methodList[i];"     nslog(@ "method----=" ">%@" , NSStringFromSelector(method_getName(method)));
}</count; i++) {></pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>獲取成員變量列表</p></li></ul><pre class= "brush:js;toolbar:false" >Ivar *ivarList = class_copyIvarList([self class], &count);
for  (unsigned int i; i<count; i++) {     ivar myivar = " ivarList[i];"      const char *ivarname = " ivar_getName(myIvar);"      nslog(@ "ivar----=" ">%@" , [NSString stringWithUTF8String:ivarName]);
}</count; i++) {></pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>獲取協議列表</p></li></ul><pre class= "brush:js;toolbar:false" >__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for  (unsigned int i; i<count; i++) {     protocol *myprotocal = " protocolList[i];"      const char *protocolname = " protocol_getName(myProtocal);"      nslog(@ "protocol----=" ">%@" , [NSString stringWithUTF8String:protocolName]);
}</count; i++) {></pre><p>現在有一個Person類,和person創建的xiaoming對象,有test1和test2兩個方法</p><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>獲得類方法</p></li></ul><pre class= "brush:js;toolbar:false" >Class PersonClass = object_getClass([Person class]);
SEL oriSEL = @selector(test1);
Method oriMethod = _class_getMethod(xiaomingClass, oriSEL);</pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>獲得實例方法</p></li></ul><pre class= "brush:js;toolbar:false" >Class PersonClass = object_getClass([xiaoming class]);
SEL oriSEL = @selector(test2);
Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL);</pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>添加方法</p></li></ul><pre class= "brush:js;toolbar:false" >BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod));</pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>替換原方法實現</p></li></ul><pre class= "brush:js;toolbar:false" >class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));</pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>交換兩個方法的實現</p></li></ul><pre class= "brush:js;toolbar:false" >method_exchangeImplementations(oriMethod, cusMethod);</pre><ul class= " list-paddingleft-2" style= "list-style-type: disc;" ><li><p>常用方法</p></li></ul><pre class= "brush:js;toolbar:false" > // 得到類的所有方法
     Method *allMethods = class_copyMethodList([Person class], &count);
// 得到所有成員變量
     Ivar *allVariables = class_copyIvarList([Person class], &count);
// 得到所有屬性
     objc_property_t *properties = class_copyPropertyList([Person class], &count);
// 根據名字得到類變量的Ivar指針,但是這個在OC中好像毫無意義
Ivar oneCVIvar = class_getClassVariable([Person class], name);
// 根據名字得到實例變量的Ivar指針
     Ivar oneIVIvar = class_getInstanceVariable([Person class], name);
// 找到後可以直接對私有變量賦值
     object_setIvar(_per, oneIVIvar, @ "Mike" ); //強制修改name屬性
/* 動態添加方法:
      第一個參數表示Class cls 類型;
      第二個參數表示待調用的方法名稱;
      第三個參數(IMP)myAddingFunction,IMP是一個函數指針,這裏表示指定具體實現方法myAddingFunction;
      第四個參數表方法的參數,0代表沒有參數;
      */
     class_addMethod([_per class], @selector(sayHi), (IMP)myAddingFunction, 0);
// 交換兩個方法
     method_exchangeImplementations(method1, method2);
 
// 關聯兩個對象
相關文章
相關標籤/搜索