前一段时间学习了Runtime,对类和对象的结构,和一些消息转发有一些自己的理解,现在希望简单的应用下,就决定自己写一个简单的JSON与Model的相互转化,现在总结下。

建议查看

 

  • 参考资料 :Runtime学习笔记http://lastdays.cn/2016/02/22/runtime/
  • 项目地址: LYModelDatahttps:///MrLoong/LYModelData

 

观察下面这个JSON数据和Model数据

 

NSString*girlFriend@"白菜";
idparmenters@{
@"girlFriend":girlFriend,
@"age":@22.1,
@"name":@"Lastdays",
@"time":@"2016-03-18 5:55:49 +0000"
};
 
@interface: NSObject
 
@propertyNSNumber*age;
@propertyNSString*name;
@propertyNSString*girlFriend;
@propertyNSData*time;
 
@end

 

开始的时候仔细想了一下,如何能够动态的去添加属性值,并且根据对应的属性进行赋值,还要保证类型正确,这是我最开始考虑的问题。但是最核心问题就是动态实现。

 

我们一步一步来解决问题,首先我们先获取Model属性,取得Model的一些信息

 

获取Model属性

 

runtime提供了class_copyPropertyList来获取属性列表,OK,我们可以来看一下用它获取的数据是什么样的?查看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(Classcls,unsignedint*outCount)
{
old_property_list *plist;
uintptr_t iterator0;
old_property **resultnil;
unsignedintcount0;
unsignedintp,i;
 
if(!cls){
if(outCount)*outCount0;
returnnil;
}
 
mutex_locker_t lock(classLock);
 
iterator0;
while((plistnextPropertyList(cls,iterator))){
countplist->count;
}
 
if(count0){
result(old_property **)malloc((count+1)sizeof(old_property *));
 
p0;
iterator0;
while((plistnextPropertyList(cls,iterator))){
for(i0;iplist->count;i++){
result[p++]property_list_nth(plist,i);
}
}
result[p]nil;
}
 
if(outCount)*outCountcount;
return(objc_property_t *)result;
}
 
typedefstructold_property *objc_property_t;
 
 
structold_property{
constchar*name;
constchar*attributes;
};
 
```

 

从上面的三段runtime源码中,课本上就能判断出,其实返回结果就是一些old_property,并且每个old_property中含有对应的name和其他信息。

 

总结起来说就是**class_copyPropertyList**获取Model属性列表,属性列表里面的objc_property_t包含着这个属性的类型和名字等一些信息。

 

根据刚才的分析设计出以下结构:

 

```bash
-(id)modelToJsonObject:(NSObject*)model{
 
Classclsself.class;
unsignedintcountProperty0;
objc_property_t *propertysclass_copyPropertyList(cls,&countProperty);
NSMutableDictionary*dic[NSMutableDictionarynew];
 
for(unsignedinti0;i<countProperty;i++){
PropertyInfo*propertyInfo[[PropertyInfoalloc]:propertys[i]];
if(propertyInfo.propertyName!=nil){
dic[propertyInfo.propertyName][self:model:propertyInfo];
}
}
returndic;
}

PropertyInfo也就是属性信息,我们将Model的所有属性存放到NSMutableDictionary中,key就是属性名,Value就是PropertyInfo。

 

接下来开始获取Model的属性信息PropertyInfo

 

我们可以通过property_getName来获取属性名,查看源码

 

constchar*property_getName(objc_property_t prop)
{
returnoldproperty(prop)->name;
}

接下来就是获取属性的类型和一些其他的信息。获取属性的信息其实和上面的原理差不多,我们使用property_copyAttributeList,查看下它的源码

 

objc_property_attribute_t *property_copyAttributeList(objc_property_t prop,
unsignedint*outCount)
{
if(!prop){
if(outCount)*outCount0;
returnnil;
}
 
mutex_locker_t lock(classLock);
returncopyPropertyAttributeList(oldproperty(prop)->attributes,outCount);
}

看到这里,不往下继续分析源码了,其实可以看到,attributes就是我们想要的信息,其实每个property也是有自己对应的attributes。

 

这个attributes是什么样呢?翻看源码,找到了答案

 

typedefstruct{
constchar*name;      
constchar*value;         
}objc_property_attribute_t;

加一下断点,看看

 

iOS开发 对象转json字符串 ios json转model_json

 

可以看到,name是T,Value是NSNumber,我们来获取下NSNumber这个属性类型。

 

for(unsignedinti0;i<attrCount;i++){
if(attrs[i].name[0]'T'){
size_t lenstrlen(attrs[i].value);
if(len>3){
charname[len2];
name[len3]'';
memcpy(name,attrs[i].value2,len3);
_typeClassobjc_getClass(name);
}
}
}

 

基本上我们想要的信息基本上都已经获取到了,现在接下来就是做动态设定。

 

中间做个插曲简单的说下Objc是动态语言,[receiver message]的执行过程当中,[receiver message]是会被动态编译的,Objc是动态语言,因此它会想尽办法将编译连接推迟到运行时来做。runtime这个时实运行系统就是来执行编译后的代码。想详细了解,欢迎阅读Runtime学习笔记

http://lastdays.cn/2016/02/22/runtime/

 

在这个消息发送过程中,objc_msgSend充当着很重要的角色,所以我们可以主动触发objc_msgSend,来模拟getter,setter方法获取属性值,或者建立。

 

我们通过SEL来定义选择器,选择器是什么?就是方法名的唯一标识符

 

根据刚才的想法,编写的代码最后是这个样子

 

-(instancetype)initWithProperty:(objc_property_t)property{
_propertyproperty;
 
constchar*nameproperty_getName(property);
if(name){
_propertyName[NSString:name];
}
unsignedintattrCount;
objc_property_attribute_t *attrsproperty_copyAttributeList(property,attrCount);
for(unsignedinti0;i<attrCount;i++){
if(attrs[i].name[0]'T'){
size_t lenstrlen(attrs[i].value);
if(len>3){
charname[len2];
name[len3]'';
memcpy(name,attrs[i].value2,len3);
_typeClassobjc_getClass(name);
}
}
}
NSString*setter[NSString:@"set%@%@:",[_propertyName:1].uppercaseString,[_propertyName:1]];
_setterNSSelectorFromString(setter);
_getterNSSelectorFromString(_propertyName);
 
returnself;
}

基本的准备工作,和一些问题都解决了,接下来可以写功能了。

 

JSON转Model

 

根据刚才说的,我们可以主动触发objc_msgSend,来模拟setter方法建立属性值。设计出以下方法

 

-(void)LYModelSetPropertyWithModel:(id)model:(id)value:(PropertyInfo*)propertyInfo{
((void(*)(id,SEL,id))(void*)objc_msgSend)((id)model,propertyInfo.setter,value);
}

 

我们将Model的所有属性存放到NSMutableDictionary中,key就是属性名,Value就是PropertyInfo。

 

现在就可以动态设定了

 

-(BOOL)LYModelSelectProperties:(NSDictionary*)dictonary{
 
ClassInfo*cls[[ClassInfoalloc]initWithClass:object_getClass(self)];
idkey,value;
NSArray*keys[dictonary allKeys];
NSUIntegercount[keys count];
for(inti0;icount;i++){
key[keys: i];
value[dictonary: key];
 
if(cls.propertyInfo[key]){
[self:self:value:cls.propertyInfo[key]];
}
}
returnYES;
}

 

完成动态设定

 

Model转JSON

 

原理跟JSON转Model

 

我们可以主动触发objc_msgSend,来模拟getter方法来获取属性值。

 

-(id)LYModelSetJsonObjectWith:(id)model:(PropertyInfo*)propertyInfo{
idvalue((id(*)(id,SEL))(void*)objc_msgSend)((id)model,propertyInfo.getter);
returnvalue;
}

建立NSDictionary

 

-(id)modelToJsonObject:(NSObject*)model{
 
Classclsself.class;
unsignedintcountProperty0;
objc_property_t *propertysclass_copyPropertyList(cls,&countProperty);
NSMutableDictionary*dic[NSMutableDictionarynew];
 
for(unsignedinti0;i<countProperty;i++){
PropertyInfo*propertyInfo[[PropertyInfoalloc]:propertys[i]];
if(propertyInfo.propertyName!=nil){
dic[propertyInfo.propertyName][self:model:propertyInfo];
}
}
returndic;
}

完成获取

 

测试

 

NSString*girlFriend@"白菜";
idparmenters@{
@"girlFriend":girlFriend,
@"age":@22.1,
@"name":@"Lastdays",
@"time":@"2016-03-18 5:55:49 +0000"
};
 
Model*model[Model:parmenters];
NSLog(@"%@",model.girlFriend);
NSLog(@"%@",);
NSLog(@"%@",model.age);
NSLog(@"%@",model.time);
 
NSLog(@"========================================");
 
NSDictionary*jsonObject= [model LYModelToJson];
NSLog(@"%@",jsonObject);

结果:

 

iOS开发 对象转json字符串 ios json转model_runtime_02

 

总结

 

简单的JSON Model转换库,关键点就是在于对runtime的理解。就当自己的一个小练习,后续会继续维护,让它对更多类型进行支持。代码结构上可能不是那么好,后续会将整体的结构重新设计下,增加可读性,也欢迎来提出建议。