本人有若干成套學習視頻, 可試看! 可試看! 可試看, 重要的事情說三遍 包含Java
, 數(shù)據(jù)結(jié)構(gòu)與算法
, iOS
, 安卓
, python
, flutter
等等, 如有需要, 聯(lián)系微信tsaievan
.
運行時(runtime)是一種面向?qū)ο蟮木幊陶Z言的運行環(huán)境
OC 最主要的特點就是在程序運行時, 以發(fā)送消息的方式調(diào)用方法
運行時時 OC 的核心, OC 就是基于運行時的
上面的話太抽象了, 運行時能干什么? 這才是最關(guān)鍵的, 當我們老是被問到 YYModel 是怎么實現(xiàn)的時候, 一臉懵逼, 其實,在 YYModel 內(nèi)部就運用了運行時, 來字典轉(zhuǎn)模型,具體的思路是這樣的
以往, 我們字典轉(zhuǎn)模型,總是需要在模型類中定義一個靜態(tài)方法或者對象方法,來字典轉(zhuǎn)模型, 這樣, 我們在不同的模型中, 都必須定義這樣一個方法來完成字典轉(zhuǎn)模型, 如果我們寫的項目比較大, 模型比較多,這樣字典轉(zhuǎn)模型的效率就太低了,耦合性也比較高, 那我們?nèi)绾巫龅阶值滢D(zhuǎn)模型 與 模型類的徹底解耦呢?
我們可以創(chuàng)建一個 NSObject 的分類, 因為所有的類(NSProxy 除外)都繼承自 NSObject, 那我們就可以用任意的類去調(diào) NSObject 的這個分類方法, 子類可以任意調(diào)用父類方法嘛
那么我們?nèi)绾卧谶@個分類方法中完成字典轉(zhuǎn)模型呢?
這里就要用到運行時的概念了,
首先我們在分類中導入 <objc/runtime.h>這個框架, 然后進行第一步,獲取屬性列表
const char *kPropertyListKey = "YFPropertyListKey";
+ (NSArray *)yf_objcProperties
{
/* 獲取關(guān)聯(lián)對象 */
NSArray *ptyList = objc_getAssociatedObject(self, kPropertyListKey);
/* 如果 ptyList 有值,直接返回 */
if (ptyList) {
return ptyList;
}
/* 調(diào)用運行時方法, 取得類的屬性列表 */
/* 成員變量:
* 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)
* 協(xié)議:
* class_copyProtocolList(__unsafe_unretained Class cls, unsigned int *outCount)
*/
unsigned int outCount = 0;
/**
* 參數(shù)1: 要獲取得類
* 參數(shù)2: 雷屬性的個數(shù)指針
* 返回值: 所有屬性的數(shù)組, C 語言中,數(shù)組的名字,就是指向第一個元素的地址
*/
/* 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++) {
/* 從數(shù)組中取得屬性 */
objc_property_t property = propertyList[i];
/* 從 property 中獲得屬性名稱 */
const char *propertyName_C = property_getName(property);
/* 將 C 字符串轉(zhuǎn)化成 OC 字符串 */
NSString *propertyName_OC = [NSString stringWithCString:propertyName_C encoding:NSUTF8StringEncoding];
[mtArray addObject:propertyName_OC];
}
/* 設(shè)置關(guān)聯(lián)對象 */
/**
* 參數(shù)1 : 對象self
* 參數(shù)2 : 動態(tài)添加屬性的 key
* 參數(shù)3 : 動態(tài)添加屬性值
* 參數(shù)4 : 對象的引用關(guān)系
*/
objc_setAssociatedObject(self, kPropertyListKey, mtArray.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
/* 釋放 */
free(propertyList);
return mtArray.copy;
}
其實上面這一長串代碼中,只有4句是最關(guān)鍵的
1./* 獲取關(guān)聯(lián)對象 */ NSArray *ptyList = objc_getAssociatedObject(self, kPropertyListKey);
如果在程序運行的時候, 模型對象的屬性是不會發(fā)生變化的, 我們在利用這個函數(shù)如果能獲取到關(guān)聯(lián)對象的屬性列表, 就不用再走下面的代碼去利用運行時再去獲取屬性列表了
2.objc_property_t *propertyList = class_copyPropertyList([self class], &outCount);
這句代碼就是真正的利用運行時獲取屬性列表, 這個屬性列表是 C 的結(jié)構(gòu)體指針數(shù)組,我們必須將其遍歷,并利用另外一個函數(shù)將取出結(jié)構(gòu)體指針所指向的結(jié)構(gòu)體中國的 C 字符串,也就是屬性名稱
3.const char *propertyName_C = property_getName(property);
獲得C字符串后,我們只需要將其轉(zhuǎn)換為 OC 字符串,加到可變數(shù)組中即可
4.objc_setAssociatedObject(self, kPropertyListKey, mtArray.copy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
設(shè)置屬性列表, 就是把已經(jīng)生成好的屬性列表設(shè)置到一個類似于屬性的東西儲存起來, 下次 get 的時候,直接拿出來用即可,有點類似于懶加載.
獲取屬性列表之后, 我們就要進行字典轉(zhuǎn)模型的操作了
首先我們要遍歷參數(shù)字典, 如果我們獲取得屬性列表中包含了字典中的 key,就利用 KVC 方法賦值,然后就完成了字典轉(zhuǎn)模型的操作
+ (instancetype)yf_objcWithDict:(NSDictionary *)dict
{
/* 實例化對象 */
id objc = [[self alloc]init];
/* 使用字典,設(shè)置對象信息 */
/* 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 設(shè)置數(shù)值 */
[objc setValue:obj forKey:key];
}
}];
/* 返回對象 */
return objc;
}
這樣, 比如我在 ViewDidLoad 方法中, 自定義一個字典
然后我只需要一行代碼就可以獲取到模型對象,如下
- (void)viewDidLoad {
[super viewDidLoad];
/* 創(chuàng)建一個字典 */
NSDictionary *dict = @{
@"name":@"小明",
@"age":@18,
@"title":@"master",
@"height":@1.7,
@"something":@"nothing"
};
Person *person = [Person yf_objcWithDict:dict];
}
而此時, 模型類中,沒有添加任何的構(gòu)造方法,只有單純的屬性,這樣就做到了徹底的解耦, 比如我現(xiàn)在再來一個學生(Student)類,我也無需添加構(gòu)造方法,也同樣只需要調(diào)用-(instancetype)yf_objcWithDict:dict;
即可.