基于Runtime实现Protobuf动态建模

技术痛点

     后台返回protobuf格式数据对于前端开发实际上并不是很便捷 , 只要增加或者减少哪怕一个字段 , 前端所有关于这个文件的所有业务全部就炸掉了 .  每次都要重新编译probuf文件(请参考我上一篇文章) .

   使用protobuf爽的一点是不用建模型 , 因为它本身就是模型 .这对于面向数据模型开发的前端是很友好的 . 凡是都有两面性 , 在不少的业务逻辑中 , 需要修改模型数值 , 这时候protobuf的弊端就展现出来了 , 数值不可更改 . 这是十分痛苦的一件事 . 所以实际上很多情况下 , 都需要自己建模(说来不如Jason) .

所以会写下大量如下的代码

基于Runtime实现Protobuf动态建模_第1张图片
滑稽.jpg

每一次建模, 都需要这样一个一个赋值 , 劳动量极大 , 并且毫无意义 .  搬砖大大降低了工作效率 . 

思考

       如何动态的去给模型赋值 ? 是的 , runtime . 关于runtime的知识 , 网上很多 , 书籍也比较多 . 这里就不赘述了 .

灵感

        在看ibireme大神的YYCategories中关于NSObject的扩展得到灵感 . 关于获取模型属性列表的部分代码也是借(chao)鉴(xi)于此 , 向大神致(gui)敬(tian) .

实现

1 . 创建TestModel , 这是头文件 .

```

#import

@interfaceTestModel :NSObject

/**集团号(测试数据)**/

@property(nonatomic,strong)NSString*clientNo;

/**用户号(测试数据)**/

@property(nonatomic,strong)NSString*userNo;

/**语言号(测试数据)**/

@property(nonatomic,strong)NSString*languageNo;

/**环信用户名(测试数据)**/

@property(nonatomic,strong)NSString*huanXinUserName;

/**环信密码(测试数据)**/

@property(nonatomic,strong)NSString*huanXinPassword;

/**集团全称(测试数据)**/

@property(nonatomic,strong)NSString*clientFullName;

/**集团简称(测试数据)**/

@property(nonatomic,strong)NSString*clientShortName;

/**用户名(测试数据)**/

@property(nonatomic,strong)NSString*userName;

/**密码(测试数据)**/

@property(nonatomic,strong)NSString*password;

/**地址(测试数据)**/

@property(nonatomic,strong)NSString*address;

/**

初始化模型

@paramdata服务器返回的还未解析的protobuf二进制文件(如果是传统Jason,也可以这么干,如果需要我会写一下)

@paramname protobuf名字

@return<#return value description#>

*/

- (instancetype)initWithData:(NSData*)data protobufName:(NSString*)name;

+ (instancetype)modelWithData:(NSData*)data protobufName:(NSString*)name;

/**

测试打印当前Model的属性值

*/

- (void)showModelProperty;

@end


xxx

```

2 . m文件

#import"TestModel.h"

#import

#import

@implementationTestModel

//初始化(对象方法)

- (instancetype)initWithData:(NSData*)data protobufName:(NSString*)name{

if(self= [superinit]) {

[self readPropertyWithProtobuf:data  protobufName:name];

}

returnself;

}

//初始化(类方法)

+ (instancetype)modelWithData:(NSData*)data protobufName:(NSString*)name{

return [[self alloc] initWithData:dataprotobuf Name:name];

}

//根据protobuf文件名字和二进制数据,生成protobuf文件并获取属性赋值给Model

- (void)readPropertyWithProtobuf:(NSData*)data protobufName:(NSString*)name{

if(data==nil) {

return;

}

//根据字符串获取类

Class modelClass =NSClassFromString(name);

//利用runtime根据类获取属性列表

NSArray*propertyAry= [self  allProperty:modelClass];

//将二进制数据转成model

id  model = [[model  Classalloc]  initWithData:dataerror:nil];

for(inti =0; i < propertyAry.count; i++) {

//获取Get方法

SEL  getSEL = [self  makeGetterWithPropertyName:propertyAry[i]];

//获取Set方法

SEL  setSEL = [self  makeSetterWithPropertyName:propertyAry[i]];

if([self  respondsToSelector:getSEL]) {

//获取类和方法的签名

NSMethodSignature*signature = [model  methodSignatureForSelector:getSEL];

//根据签名获得调用对象

NSInvocation*invocation = [NSInvocation  invocationWithMethodSignature:signature];

[invocation  setTarget:model];

[invocation  setSelector:getSEL];

NSObject*__unsafe_unretainedreturnValue =nil;

[invocationinvoke];

//获取protobuf属性返回值(如同jason的根据key获取value)

[invocation  getReturnValue:&returnValue];

if(returnValue !=nil) {

//赋值给Model(关键步骤)

[self  performSelectorOnMainThread:setSEL  withObject:returnValuewaitUntilDone:[NSThread  mainThread]];

}

}

}

}

//获取属性(借鉴于ibireme)

- (NSArray*)allProperty:(Class)class{

unsigned intproperty Count =0;

objc_property_t* properties =class_copyPropertyList(class, &propertyCount);

NSMutableArray* propertyNames = [NSMutableArray array];

for(unsigned int i =0; i < propertyCount; ++i) {

objc_property_tproperty = properties[i];

constchar* name =property_getName(property);

[property NamesaddObject:[NSString stringWithUTF8String:name]];

}

free(properties);

return propertyNames;

}

//根据字符串生成get方法

- (SEL)makeGetterWithPropertyName:(NSString*)propertyName{

return  NSSelectorFromString(propertyName);

}

//根据字符串生成set方法

- (SEL)makeSetterWithPropertyName:(NSString*)propertyName{

//首字母大写

NSString*firstWord = [property  NamesubstringToIndex:1];

firstWord = [firstWord  uppercaseString];

propertyName = [NSString  stringWithFormat:@"%@%@",firstWord , [propertyName  substringFromIndex:1]];

//拼接上set

propertyName = [NSString  stringWithFormat:@"set%@:", propertyName];

return  NSSelectorFromString(propertyName);

}

//测试是否赋值成功,可以删掉.

- (void)showModelProperty{

//获取实体类的属性名

NSArray*array = [self  allProperty:self.class];

//拼接参数

NSMutableString*resultString = [[NSMutable  Stringalloc]  init];

for(inti =0; i < array.count; i ++) {

//获取get方法

SEL getSel = [self  makeGetterWithPropertyName:array[i]];

if([self  respondsToSelector:getSel]) {

//获得类和方法的签名

NSMethodSignature*signature = [self  methodSignatureForSelector:getSel];

//从签名获得调用对象

NSInvocation*invocation = [NSInvocation  invocationWithMethodSignature:signature];

//设置target

[invocation  setTarget:self];

//设置selector

[invocation  setSelector:getSel];

//接收返回的值

NSObject*__unsafe_unretainedreturnValue =nil;

//调用

[invocation  invoke];

//接收返回值

[invocation  getReturnValue:&returnValue];

[resultString  appendFormat:@"%@\n", returnValue];

}

}

NSLog(@"%@", resultString);

}

@end


xxxx


调用

//"LoginResMsg"就是protobuf文件的名称字符串userInfo就是二进制数据 .

TestModel*model = [[TestModel  alloc]  initWithDictionary:userInfo protobufName:@"LoginResMsg"];

//测试是否赋值成功

[model showModelProperty];

打印


基于Runtime实现Protobuf动态建模_第2张图片
滑稽.jpg

完结

因为Jason的解析已经有很多成熟的框架 , YYModel , MJExtension 等等 .protobuf这东西用得不多 , 请参考 .

你可能感兴趣的:(基于Runtime实现Protobuf动态建模)