iOS 反射 學習 和 運用
反射: 通過 類名來獲得生成的相應的類的實例 的這種機制 叫 反射
常用的反射方式
(1)第一種 其實很好實現的 ,你可以自己寫映射關系 對應 ,也可以使用一些優秀的第三方,比如 MJExtension 等 特別方便
(2)MJExtension 有一個方法 valueKey, 這個 是可以轉字典
但是這種方法是有局限的 當這個 比較適合 單層次的model類 不適合 自己子類 有自定義model 或者 自己本身是繼承於其他自定義model的派生類
因為很多時候我想轉成字典 我是想把它通過字典轉json 做緩存 ,如果 這個字典 中有自定義類 (另外 如果有UI類型的類 比如 UIColor UIView UILabel 等非數據級別的) 會在轉成json的過程中失敗 而崩潰.
在網上查了 好多資料 有很多類似的說法
”對象的類,如果是派生類,就得靠其他方式來實現了,因為得到不該基類的屬性” (錯誤的一句話)
看到 各種轉載 之後 甚至 自己都怯步了 靠 我能做到嗎?
現在我可以說 能啊 用腳想 實際運用的時候 不就是通過點語法獲取 父類/子類的屬性 或者方法的麼 反過來 為啥不行 ?
Java C# 反射很常見 iOS也一定行!!!
關鍵代碼來了
先上幾個要點
反射 動用了 底層 主要是 c 語言
(1)頭文件 #import <objc/runtime.h>
(2) 算法策略 DFS
(3) 通過調用自身的class方法獲取當前類的元數據信息
通過runtime的 class_copyPropertyList 方法取得當前類的屬性列表,以指針數組的形 式返 回
遍歷指針數組,通過property_getName獲取屬性名,property_getAttributes獲取屬性類型
(4)判斷條件
a. 用[value class]得到的都是 JKArray JKdictionary __NSCFString __NSCFConstantString 等 類簇級別的 他們的父類的父類 或者說 他們 是 NSString 等 的一個小子類,判斷冗余條件太多 不容易窮盡 也不夠科學 所以 獲取類 必須用 property_getAttributes來取 才能取到 NSString NSArray 等 NS級別的
轉化後 是一個字符串 類型的類
再轉換成類 方法是:
Class propertyClass = NSClassFromString(className);
這種方法優勢是動態獲取 甚至不用 添加 頭文件 來制約 甚是方便,因為 類 在搜索過程中 他本來就是從物理條件 匿名 出發 你不可能提前角度 知道 你將要搜索到什麼類
b. 關鍵就是 節點檢索條件
我設計的是
向下:如果當前節點 是一個 系統級別的類 那麼它已經是葉子節點 否則 需要進一步DFS
向上:DFS 當前類的 superClass
結束條件: class 為空 或者 已經到 基類 NSObject
判斷 是否 是系統級別的類方法
if (propertyClass == NULL) { } else { NSBundle *mainB = [NSBundle bundleForClass:propertyClass]; if (mainB == [NSBundle mainBundle]) { DLog(@"自定義的類"); } else { DLog(@"系統的類"); // if (propertyClass == [UIColor class]) { //看需求 處理 // continue; // } } }
原理:就是看這個類 是不是在沙盒裡 在 肯定是系統級別的,否則 是 自定義的
上代碼:
// // NSDictionary+ModelConvert.h // dailylife // // Created by HF on 16/3/9. // // #import <Foundation/Foundation.h> @interface NSDictionary (ModelConvert) /** * 拓展 獲得當前類 及 子類 和 父類 的全部 屬性和屬性值 * get current object as well as the properties inherited from the parent classes * @returns (NSString) Dictionary of property name --> value * * 注意 不應該 使用 UI 的類 做處理 可以生成字典 但是轉json會崩潰 */ + (NSDictionary *)toDictionaryFromModel:(id)objectModel; @end // // NSDictionary+ModelConvert.m // dailylife // // Created by HF on 16/3/9. // // #import "NSDictionary+ModelConvert.h" #import <objc/runtime.h> @implementation NSDictionary (ModelConvert) + (NSDictionary *)toDictionaryFromModel:(id)objectModel{ return [NSDictionary classPropsForClassHierarchyObjectModel:objectModel class:[objectModel class] onDictionary:[NSMutableDictionary dictionary]]; } static const char *getPropertyType(objc_property_t property) { const char *attributes = property_getAttributes(property); //printf("attributes=%s\n", attributes); char buffer[1 + strlen(attributes)]; strcpy(buffer, attributes); char *state = buffer, *attribute; while ((attribute = strsep(&state, ",")) != NULL) { if (attribute[0] == 'T' && attribute[1] != '@') { // it's a C primitive type: // if you want a list of what will be returned for these primitives, search online for // "objective-c" "Property Attribute Description Examples" // apple docs list plenty of examples of what you get for int "i", long "l", unsigned "I", struct, etc. NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding]; return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding]; } else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) { // it's an ObjC id type: return "id"; } else if (attribute[0] == 'T' && attribute[1] == '@') { // it's another ObjC object type: NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding]; return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding]; } } return ""; } + (NSDictionary *)classPropsForClassHierarchyObjectModel:(id)objectModel class:(Class)klass onDictionary:(NSMutableDictionary *)results { if (klass == NULL) { return nil; } //stop if we reach the NSObject class as is the base class if (klass == [NSObject class]) { return [NSDictionary dictionaryWithDictionary:results]; } else{ unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList(klass, &outCount); if (outCount == 0) { free(properties); return nil; } for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; const char *propName = property_getName(property); if(propName) { NSString *propertyName = [NSString stringWithUTF8String:propName]; id value = [objectModel valueForKey:propertyName]; if (value) { const char *propType = getPropertyType(property); NSString *className = [NSString stringWithCString:propType encoding:[NSString defaultCStringEncoding]]; Class propertyClass = NSClassFromString(className); DLog(@"%@ == %@",className,NSClassFromString(className)); if (propertyClass == NULL) { [results setObject:value forKey:propertyName]; } else { DLog(@"%@",propertyName); DLog(@"%@",value); DLog(@"%@",[value class]); NSBundle *mainB = [NSBundle bundleForClass:propertyClass]; if (mainB == [NSBundle mainBundle]) { DLog(@"自定義的類"); NSDictionary *dic = [NSDictionary toDictionaryFromModel:value]; //向下處理 [results setObject:dic forKey:propertyName]; } else { DLog(@"系統的類"); if (propertyClass == [UIColor class]) {//看需求處理 continue; } [results setObject:value forKey:propertyName]; } } } } } free(properties); //go for the superclass return [NSDictionary classPropsForClassHierarchyObjectModel:objectModel class:[klass superclass] onDictionary:results];//向上處理 } } @end
參考 多重繼承類
1.
2.
3.
使用:
運行打印
ShowDataFeedModel 還有個屬性 是屬性 是 foodTags 這裡沒打印 出來 是因為 value是空的 我沒有 把 value為空的對象 放在 字典集合中 如果 需要的話 可以處理的 放進空數據就好了
這樣的字典集合 可以直接轉換成 json 存儲 需要的 時候 再轉換成 對應的 數據model 妥妥的