建議檢視
- 參考資料 :Runtime學習筆記
- 專案地址: LYModelData
觀察下面這個JSON資料和Model資料
1 2 3 4 5 6 7 |
NSString *girlFriend = @"白菜"; id parmenters = @{ @"girlFriend":girlFriend, @"age":@22.1, @"name":@"Lastdays", @"time":@"2016-03-18 5:55:49 +0000" }; |
1 2 3 4 5 6 7 8 |
@interface Model : NSObject @property NSNumber *age; @property NSString *name; @property NSString *girlFriend; @property NSData *time; @end |
開始的時候仔細想了一下,如何能夠動態的去新增屬性值,並且根據對應的屬性進行賦值,還要保證型別正確,這是我最開始考慮的問題。但是最核心問題就是動態實現。
我們一步一步來解決問題,首先我們先獲取Model屬性,取得Model的一些資訊
獲取Model屬性
runtime提供了class_copyPropertyList來獲取屬性列表,OK,我們可以來看一下用它獲取的資料是什麼樣的?檢視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 |
/*********************************************************************** * class_copyPropertyList. Returns a heap block containing the * properties declared in the class, or nil if the class * declares no properties. Caller must free the block. * Does not copy any superclass's properties. **********************************************************************/ objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount) { old_property_list *plist; uintptr_t iterator = 0; old_property **result = nil; unsigned int count = 0; unsigned int p, i; if (!cls) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(classLock); iterator = 0; while ((plist = nextPropertyList(cls, &iterator))) { count += plist->count; } if (count > 0) { result = (old_property **)malloc((count+1) * sizeof(old_property *)); p = 0; iterator = 0; while ((plist = nextPropertyList(cls, &iterator))) { for (i = 0; i < plist->count; i++) { result[p++] = property_list_nth(plist, i); } } result[p] = nil; } if (outCount) *outCount = count; return (objc_property_t *)result; } |
1 |
typedef struct old_property *objc_property_t; |
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 |
struct old_property { const char *name; const char *attributes; }; ``` 從上面的三段runtime原始碼中,課本上就能判斷出,其實返回結果就是一些old_property,並且每個old_property中含有對應的name和其他資訊。 總結起來說就是**class_copyPropertyList**獲取Model屬性列表,屬性列表裡面的objc_property_t包含著這個屬性的型別和名字等一些資訊。 根據剛才的分析設計出以下結構: ``` bash -(id)modelToJsonObject:(NSObject *)model{ Class cls = self.class; unsigned int countProperty = 0; objc_property_t *propertys = class_copyPropertyList(cls,&countProperty); NSMutableDictionary *dic = [NSMutableDictionary new]; for (unsigned int i = 0; i<countProperty; i++) { PropertyInfo *propertyInfo = [[PropertyInfo alloc] initWithProperty:propertys[i]]; if (propertyInfo.propertyName!=nil) { dic[propertyInfo.propertyName] = [self LYModelSetJsonObjectWith:model propertyInfo:propertyInfo]; } } return dic; } |
PropertyInfo也就是屬性資訊,我們將Model的所有屬性存放到NSMutableDictionary中,key就是屬性名,Value就是PropertyInfo。
接下來開始獲取Model的屬性資訊PropertyInfo
我們可以通過property_getName來獲取屬性名,檢視原始碼
1 2 3 4 |
const char *property_getName(objc_property_t prop) { return oldproperty(prop)->name; } |
接下來就是獲取屬性的型別和一些其他的資訊。獲取屬性的資訊其實和上面的原理差不多,我們使用property_copyAttributeList,檢視下它的原始碼
1 2 3 4 5 6 7 8 9 10 11 |
objc_property_attribute_t *property_copyAttributeList(objc_property_t prop, unsigned int *outCount) { if (!prop) { if (outCount) *outCount = 0; return nil; } mutex_locker_t lock(classLock); return copyPropertyAttributeList(oldproperty(prop)->attributes,outCount); } |
看到這裡,不往下繼續分析原始碼了,其實可以看到,attributes就是我們想要的資訊,其實每個property也是有自己對應的attributes。
這個attributes是什麼樣呢?翻看原始碼,找到了答案
1 2 3 4 |
typedef struct { const char *name; const char *value; } objc_property_attribute_t; |
加一下斷點,看看
可以看到,name是T,Value是NSNumber,我們來獲取下NSNumber這個屬性型別。
1 2 3 4 5 6 7 8 9 10 11 |
for (unsigned int i = 0; i<attrCount; i++) { if (attrs[i].name[0] == 'T') { size_t len = strlen(attrs[i].value); if (len>3) { char name[len - 2]; name[len - 3] = ''; memcpy(name, attrs[i].value + 2, len - 3); _typeClass = objc_getClass(name); } } } |
基本上我們想要的資訊基本上都已經獲取到了,現在接下來就是做動態設定。
中間做個插曲簡單的說下Objc是動態語言,[receiver message]的執行過程當中,[receiver message]是會被動態編譯的,Objc是動態語言,因此它會想盡辦法將編譯連線推遲到執行時來做。runtime這個時實執行系統就是來執行編譯後的程式碼。想詳細瞭解,歡迎閱讀Runtime學習筆記
在這個訊息傳送過程中,objc_msgSend充當著很重要的角色,所以我們可以主動觸發objc_msgSend,來模擬getter,setter方法獲取屬性值,或者建立。
我們通過SEL來定義選擇器,選擇器是什麼?就是方法名的唯一識別符號
根據剛才的想法,編寫的程式碼最後是這個樣子
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 |
-(instancetype)initWithProperty:(objc_property_t)property{ _property = property; const char *name = property_getName(property); if (name) { _propertyName = [NSString stringWithUTF8String:name]; } unsigned int attrCount; objc_property_attribute_t *attrs = property_copyAttributeList(property, &attrCount); for (unsigned int i = 0; i<attrCount; i++) { if (attrs[i].name[0] == 'T') { size_t len = strlen(attrs[i].value); if (len>3) { char name[len - 2]; name[len - 3] = ''; memcpy(name, attrs[i].value + 2, len - 3); _typeClass = objc_getClass(name); } } } NSString *setter = [NSString stringWithFormat:@"set%@%@:", [_propertyName substringToIndex:1].uppercaseString, [_propertyName substringFromIndex:1]]; _setter = NSSelectorFromString(setter); _getter = NSSelectorFromString(_propertyName); return self; } |
基本的準備工作,和一些問題都解決了,接下來可以寫功能了。
JSON轉Model
根據剛才說的,我們可以主動觸發objc_msgSend,來模擬setter方法建立屬性值。設計出以下方法
1 2 3 |
-(void)LYModelSetPropertyWithModel:(id) model value:(id)value propertyInfo:(PropertyInfo *) propertyInfo{ ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, propertyInfo.setter, value); } |
我們將Model的所有屬性存放到NSMutableDictionary中,key就是屬性名,Value就是PropertyInfo。
現在就可以動態設定了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
-(BOOL)LYModelSelectProperties:(NSDictionary *)dictonary{ ClassInfo *cls = [[ClassInfo alloc]initWithClass:object_getClass(self)]; id key, value; NSArray *keys = [dictonary allKeys]; NSUInteger count = [keys count]; for (int i = 0; i < count; i++){ key = [keys objectAtIndex: i]; value = [dictonary objectForKey: key]; if (cls.propertyInfo[key]) { [self LYModelSetPropertyWithModel:self value:value propertyInfo:cls.propertyInfo[key]]; } } return YES; } |
完成動態設定
Model轉JSON
原理跟JSON轉Model
我們可以主動觸發objc_msgSend,來模擬getter方法來獲取屬性值。
1 2 3 4 |
-(id)LYModelSetJsonObjectWith:(id)model propertyInfo:(PropertyInfo *)propertyInfo{ id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyInfo.getter); return value; } |
建立NSDictionary
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
-(id)modelToJsonObject:(NSObject *)model{ Class cls = self.class; unsigned int countProperty = 0; objc_property_t *propertys = class_copyPropertyList(cls,&countProperty); NSMutableDictionary *dic = [NSMutableDictionary new]; for (unsigned int i = 0; i<countProperty; i++) { PropertyInfo *propertyInfo = [[PropertyInfo alloc] initWithProperty:propertys[i]]; if (propertyInfo.propertyName!=nil) { dic[propertyInfo.propertyName] = [self LYModelSetJsonWith:model propertyInfo:propertyInfo]; } } return dic; } |
完成獲取
測試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
NSString *girlFriend = @"白菜"; id parmenters = @{ @"girlFriend":girlFriend, @"age":@22.1, @"name":@"Lastdays", @"time":@"2016-03-18 5:55:49 +0000" }; Model *model = [Model LYModelWithJSON:parmenters]; NSLog(@"%@",model.girlFriend); NSLog(@"%@",model.name); NSLog(@"%@",model.age); NSLog(@"%@",model.time); NSLog(@"========================================"); NSDictionary *jsonObject= [model LYModelToJson]; NSLog(@"%@",jsonObject); |
結果:
總結
簡單的JSON Model轉換庫,關鍵點就是在於對runtime的理解。就當自己的一個小練習,後續會繼續維護,讓它對更多型別進行支援。程式碼結構上可能不是那麼好,後續會將整體的結構重新設計下,增加可讀性,也歡迎來提出建議。