iOS手動(dòng)打造JSON Model轉(zhuǎn)換庫
觀察下面這個(gè)JSON數(shù)據(jù)和Model數(shù)據(jù)
- NSString *girlFriend = @"白菜";
- id parmenters = @{
- @"girlFriend":girlFriend,
- @"age":@22.1,
- @"name":@"Lastdays",
- @"time":@"2016-03-18 5:55:49 +0000"
- };
- @interfaceModel: NSObject
- @property NSNumber *age;
- @property NSString *name;
- @property NSString *girlFriend;
- @property NSData *time;
- @end
開始的時(shí)候仔細(xì)想了一下,如何能夠動(dòng)態(tài)的去添加屬性值,并且根據(jù)對應(yīng)的屬性進(jìn)行賦值,還要保證類型正確,這是我最開始考慮的問題。但是最核心問題就是 動(dòng)態(tài)實(shí)現(xiàn) 。
我們一步一步來解決問題,首先我們先獲取Model屬性,取得Model的一些信息
獲取Model屬性
runtime提供了 class_copyPropertyList 來獲取屬性列表,OK,我們可以來看一下用它獲取的數(shù)據(jù)是什么樣的?查看runtime源碼
- /***********************************************************************
- * 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_titerator = 0;
- old_property**result = nil;
- unsigned int count = 0;
- unsigned int p, i;
- if (!cls) {
- if (outCount) *outCount = 0;
- return nil;
- }
- mutex_locker_tlock(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;
- }
- typedef struct old_property*objc_property_t;
- struct old_property {
- const char *name;
- const char *attributes;
- };
從上面的三段runtime源碼中,課本上就能判斷出,其實(shí)返回結(jié)果就是一些old_property,并且每個(gè)old_property中含有對應(yīng)的name和其他信息。
總結(jié)起來說就是**class_copyPropertyList**獲取Model屬性列表,屬性列表里面的objc_property_t包含著這個(gè)屬性的類型和名字等一些信息。
根據(jù)剛才的分析設(shè)計(jì)出以下結(jié)構(gòu):
- 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] = [selfLYModelSetJsonObjectWith:modelpropertyInfo:propertyInfo];
- }
- }
- return dic;
- }
PropertyInfo也就是屬性信息,我們將Model的所有屬性存放到 NSMutableDictionary 中,key就是屬性名,Value就是PropertyInfo。
接下來開始獲取Model的屬性信息 PropertyInfo
我們可以通過property_getName來獲取屬性名
- const char *property_getName(objc_property_tprop)
- {
- return oldproperty(prop)->name;
- }
接下來就是獲取屬性的類型和一些其他的信息。獲取屬性的信息其實(shí)和上面的原理差不多,我們使用property_copyAttributeList,
- objc_property_attribute_t*property_copyAttributeList(objc_property_tprop,
- unsigned int *outCount)
- {
- if (!prop) {
- if (outCount) *outCount = 0;
- return nil;
- }
- mutex_locker_tlock(classLock);
- return copyPropertyAttributeList(oldproperty(prop)->attributes,outCount);
- }
看到這里,不往下繼續(xù)分析源碼了,其實(shí)可以看到,attributes就是我們想要的信息,其實(shí)每個(gè)property也是有自己對應(yīng)的attributes。
這個(gè)attributes是什么樣呢?翻看源碼,找到了答案
- typedef struct {
- const char *name;
- const char *value;
- } objc_property_attribute_t;
加一下斷點(diǎn),看看
可以看到,name是T,Value是NSNumber,我們來獲取下NSNumber這個(gè)屬性類型。
- for (unsigned int i = 0; i<attrCount; i++) {
- if (attrs[i].name[0] == 'T') {
- size_tlen = 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);
- }
- }
- }
基本上我們想要的信息基本上都已經(jīng)獲取到了,現(xiàn)在接下來就是做動(dòng)態(tài)設(shè)定。
中間做個(gè)插曲簡單的說下Objc是動(dòng)態(tài)語言,[receiver message]的執(zhí)行過程當(dāng)中,[receiver message]是會被動(dòng)態(tài)編譯的,Objc是動(dòng)態(tài)語言,因此它會想盡辦法將編譯連接推遲到運(yùn)行時(shí)來做。runtime這個(gè)時(shí)實(shí)運(yùn)行系統(tǒng)就是來執(zhí)行編譯后的代碼。
在這個(gè)消息發(fā)送過程中,objc_msgSend充當(dāng)著很重要的角色,所以我們可以主動(dòng)觸發(fā)objc_msgSend,來模擬getter,setter方法獲取屬性值,或者建立。
我們通過SEL來定義選擇器,選擇器是什么?就是方法名的唯一標(biāo)識符
根據(jù)剛才的想法,編寫的代碼***是這個(gè)樣子
- -(instancetype)initWithProperty:(objc_property_t)property{
- _property = property;
- const char *name = property_getName(property);
- if (name) {
- _propertyName = [NSStringstringWithUTF8String: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_tlen = 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 = [NSStringstringWithFormat:@"set%@%@:", [_propertyNamesubstringToIndex:1].uppercaseString, [_propertyNamesubstringFromIndex:1]];
- _setter = NSSelectorFromString(setter);
- _getter = NSSelectorFromString(_propertyName);
- return self;
- }
基本的準(zhǔn)備工作,和一些問題都解決了,接下來可以寫功能了。
JSON轉(zhuǎn)Model
根據(jù)剛才說的,我們可以主動(dòng)觸發(fā)objc_msgSend,來模擬setter方法建立屬性值。設(shè)計(jì)出以下方法
- -(void)LYModelSetPropertyWithModel:(id) modelvalue:(id)valuepropertyInfo:(PropertyInfo *) propertyInfo{
- ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, propertyInfo.setter, value);
- }
我們將Model的所有屬性存放到 NSMutableDictionary 中,key就是屬性名,Value就是PropertyInfo。
現(xiàn)在就可以動(dòng)態(tài)設(shè)定了
- -(BOOL)LYModelSelectProperties:(NSDictionary *)dictonary{
- ClassInfo *cls = [[ClassInfo alloc]initWithClass:object_getClass(self)];
- id key, value;
- NSArray *keys = [dictonaryallKeys];
- NSUInteger count = [keyscount];
- for (int i = 0; i < count; i++){
- key = [keysobjectAtIndex: i];
- value = [dictonaryobjectForKey: key];
- if (cls.propertyInfo[key]) {
- [selfLYModelSetPropertyWithModel:selfvalue:valuepropertyInfo:cls.propertyInfo[key]];
- }
- }
- return YES;
- }
完成動(dòng)態(tài)設(shè)定
Model轉(zhuǎn)JSON
原理跟JSON轉(zhuǎn)Model
我們可以主動(dòng)觸發(fā)objc_msgSend,來模擬getter方法來獲取屬性值。
- -(id)LYModelSetJsonObjectWith:(id)modelpropertyInfo:(PropertyInfo *)propertyInfo{
- id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyInfo.getter);
- return value;
- }
建立NSDictionary
- -(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] = [selfLYModelSetJsonWith:modelpropertyInfo:propertyInfo];
- }
- }
- return dic;
- }
完成獲取
測試
- NSString *girlFriend = @"白菜";
- id parmenters = @{
- @"girlFriend":girlFriend,
- @"age":@22.1,
- @"name":@"Lastdays",
- @"time":@"2016-03-18 5:55:49 +0000"
- };
- Model *model = [ModelLYModelWithJSON:parmenters];
- NSLog(@"%@",model.girlFriend);
- NSLog(@"%@",model.name);
- NSLog(@"%@",model.age);
- NSLog(@"%@",model.time);
- NSLog(@"========================================");
- NSDictionary *jsonObject= [modelLYModelToJson];
- NSLog(@"%@",jsonObject);
結(jié)果:
總結(jié)
簡單的JSON Model轉(zhuǎn)換庫,關(guān)鍵點(diǎn)就是在于對runtime的理解。就當(dāng)自己的一個(gè)小練習(xí),后續(xù)會繼續(xù)維護(hù),讓它對更多類型進(jìn)行支持。代碼結(jié)構(gòu)上可能不是那么好,后續(xù)會將整體的結(jié)構(gòu)重新設(shè)計(jì)下,增加可讀性,也歡迎來提出建議。