運行時(runtime)是一種面向對象的編程語言的運行環境
OC 最主要的特點就是在程序運行時, 以發送消息的方式調用方法
運行時時 OC 的核心, OC 就是基於運行時的
上面的話太抽象了, 運行時能干什麼? 這才是最關鍵的, 當我們老是被問到 YYModel 是怎麼實現的時候, 一臉懵逼, 其實,在 YYModel 內部就運用了運行時, 來字典轉模型,具體的思路是這樣的
字典轉模型的核心算法思路
以往, 我們字典轉模型,總是需要在模型類中定義一個靜態方法或者對象方法,來字典轉模型, 這樣, 我們在不同的模型中, 都必須定義這樣一個方法來完成字典轉模型, 如果我們寫的項目比較大, 模型比較多,這樣字典轉模型的效率就太低了,耦合性也比較高, 那我們如何做到字典轉模型 與 模型類的徹底解耦呢?
我們可以創建一個 NSObject 的分類, 因為所有的類(NSProxy 除外)都繼承自 NSObject, 那我們就可以用任意的類去調 NSObject 的這個分類方法, 子類可以任意調用父類方法嘛
那麼我們如何在這個分類方法中完成字典轉模型呢?
這裡就要用到運行時的概念了,
首先我們在分類中導入
const char *kPropertyListKey = "YFPropertyListKey"; + (NSArray *)yf_objcProperties { /* 獲取關聯對象 */ NSArray *ptyList = objc_getAssociatedObject(self, kPropertyListKey); /* 如果 ptyList 有值,直接返回 */ if (ptyList) { return ptyList; } /* 調用運行時方法, 取得類的屬性列表 */ /* 成員變量: * class_copyIvarList(__unsafe_unretained Class cls, unsigned int *outCount) * 方法: * class_copyMethodList(__unsafe_unretained Class cls, unsigned int *outCount) * 屬性: * class_copyPropertyList(__unsafe_unretained Class cls, unsigned int *outCount) * 協議: * class_copyProtocolList(__unsafe_unretained Class cls, unsigned int *outCount) */ unsigned int outCount = 0; /** * 參數1: 要獲取得類 * 參數2: 雷屬性的個數指針 * 返回值: 所有屬性的數組, C 語言中,數組的名字,就是指向第一個元素的地址 */ /* retain, creat, copy 需要release */ objc_property_t *propertyList = class_copyPropertyList([self class], &outCount); NSMutableArray *mtArray = [NSMutableArray array]; /* 遍歷所有屬性 */ for (unsigned int i = 0; i < outCount; i++) { /* 從數組中取得屬性 */ objc_property_t property = propertyList[i]; /* 從 property 中獲得屬性名稱 */ const char *propertyName_C = property_getName(property); /* 將 C 字符串轉化成 OC 字符串 */ NSString *propertyName_OC = [NSString stringWithCString:propertyName_C encoding:NSUTF8StringEncoding]; [mtArray addObject:propertyName_OC]; } /* 設置關聯對象 */ /** * 參數1 : 對象self * 參數2 : 動態添加屬性的 key * 參數3 : 動態添加屬性值 * 參數4 : 對象的引用關系 */ objc_setAssociatedObject(self, kPropertyListKey, mtArray.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC); /* 釋放 */ free(propertyList); return mtArray.copy; }
其實上面這一長串代碼中,只有4句是最關鍵的
1./* 獲取關聯對象 */ NSArray *ptyList = objc_getAssociatedObject(self, kPropertyListKey);
如果在程序運行的時候, 模型對象的屬性是不會發生變化的, 我們在利用這個函數如果能獲取到關聯對象的屬性列表, 就不用再走下面的代碼去利用運行時再去獲取屬性列表了
2.objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
這句代碼就是真正的利用運行時獲取屬性列表, 這個屬性列表是 C 的結構體指針數組,我們必須將其遍歷,並利用另外一個函數將取出結構體指針所指向的結構體中國的 C 字符串,也就是屬性名稱
3.const char *propertyName_C = property_getName(property);
獲得C字符串後,我們只需要將其轉換為 OC 字符串,加到可變數組中即可
4.objc_setAssociatedObject(self, kPropertyListKey, mtArray.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
設置屬性列表, 就是把已經生成好的屬性列表設置到一個類似於屬性的東西儲存起來, 下次 get 的時候,直接拿出來用即可,有點類似於懶加載.
獲取屬性列表之後, 我們就要進行字典轉模型的操作了
首先我們要遍歷參數字典, 如果我們獲取得屬性列表中包含了字典中的 key,就利用 KVC 方法賦值,然後就完成了字典轉模型的操作
+ (instancetype)yf_objcWithDict:(NSDictionary *)dict { /* 實例化對象 */ id objc = [[self alloc]init]; /* 使用字典,設置對象信息 */ /* 1. 獲得 self 的屬性列表 */ NSArray *propertyList = [self yf_objcProperties]; /* 2. 遍歷字典 */ [dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { /* 3. 判斷 key 是否字 propertyList 中 */ if ([propertyList containsObject:key]) { /* 說明屬性存在,可以使用 KVC 設置數值 */ [objc setValue:obj forKey:key]; } }]; /* 返回對象 */ return objc; }
這樣, 比如我在 ViewDidLoad 方法中, 自定義一個字典
然後我只需要一行代碼就可以獲取到模型對象,如下
- (void)viewDidLoad { [super viewDidLoad]; /* 創建一個字典 */ NSDictionary *dict = @{ @"name":@"小明", @"age":@18, @"title":@"master", @"height":@1.7, @"something":@"nothing" }; Person *person = [Person yf_objcWithDict:dict]; }
而此時, 模型類中,沒有添加任何的構造方法,只有單純的屬性,這樣就做到了徹底的解耦, 比如我現在再來一個學生(Student)類,我也無需添加構造方法,也同樣只需要調用-(instancetype)yf_objcWithDict:dict;即可.
模型類只有單純的屬性
一行代碼完成字典轉模型
這就是一些第三方框架,例如 YYModel,MJExtension等的核心算法,當然,他們會做很多優化, 因為實際上的字典轉模型是很復雜的, 字典裡嵌套數組,數組裡有數組,數組裡有字典,字典裡又有數組等等, 這我就 hold不住啦,感謝各位捧場!