打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
用Runtime的手段填充任意NSObject对象的nil属性

前言

好久没有写东西了,忙啊。

前段时间参加了一下我们华科联创的HackDay(本人在读研=。=,目前在阿里实习),作品是一款  实时在线对战游戏 - 波波攒 ,(介绍请看  知乎 ) 

从iOS游戏客户端(用的  SpriteKit )到后台(  PHP CI + Node + SocketIO + MySQL )全是自己一个人倒腾出来的,做了一把真正的全栈工程师,爽啊~ 

后面会完善整个游戏,增加角色、优化啥的,过上一段时间会上线的哈~

回到正文,本文主要介绍了怎么用Runtime的手段遍历任意NSObject对象的所有property,检查其值是否是nil,是的话根据其类型为其填充一个默认值。 

Runtime毕竟是个“危险”的技术,本文的代码只是个初步的尝试。

初衷

在做项目的过程中,总是会写一大堆if、else语句去检查对象的Property是否是nil,如从服务器返回的JSON中缺少属性,导致Entity的某些值为空;或者创建的对象没有对所有属性做初始化等等。写多了觉得好烦啊=。=所以想到本文的方法,嗯,程序员总是懒的。 

解决步骤

  1. 遍历一个对象的所有属性(默认不包括父类属性)。
  2. 判断属性是否是nil。
  3. 为nil的属性,获取它的类型。
  4. 根据类型设置初始值(如NSString可以设为空字符串;NSNumber可以设为@0)

Runtime

OC是一门“动态”、“基于消息”的语言,而Runtime就是利用OC的动态特性,在运行时对程序做出“调整”的技术。有关Runtime的官方文档、网上的资料很多,大家自学哈~

本文主要用了如下几个Runtime的函数: 

// 获取类的所有Property1. objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)// 获取一个Property的变量名2. const char *property_getName(objc_property_t property)// 获取一个Property的详细类型表达字符串3. const char *property_getAttributes(objc_property_t property)

示例

不好一块一块拆开说,直接上代码: 

/** * 解析Property的Attributed字符串,参考Stackoverflow */static const char *getPropertyType(objc_property_t property) {    const char *attributes = property_getAttributes(property);        NSLog(@"%s", attributes);        char buffer[1 + strlen(attributes)];    strcpy(buffer, attributes);    char *state = buffer, *attribute;    while ((attribute = strsep(&state, ",")) != NULL) {        // 非对象类型        if (attribute[0] == 'T' && attribute[1] != '@') {            // 利用NSData复制一份字符串            return (const char *) [[NSData dataWithBytes:(attribute + 1) length:strlen(attribute) - 1] bytes];        // 纯id类型        } else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {            return "id";        // 对象类型        } else if (attribute[0] == 'T' && attribute[1] == '@') {            return (const char *) [[NSData dataWithBytes:(attribute + 3) length:strlen(attribute) - 4] bytes];        }    }    return "";}/** * 给对象的属性设置默认值 */void checkEntity(NSObject *object) {    // 不同类型的字符串表示,目前只是简单检查字符串、数字、数组    static const char *CLASS_NAME_NSSTRING;    static const char *CLASS_NAME_NSNUMBER;    static const char *CLASS_NAME_NSARRAY;        // 初始化类型常量    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        // "NSString"        CLASS_NAME_NSSTRING =  NSStringFromClass([NSString class]).UTF8String;        // "NSNumber        CLASS_NAME_NSNUMBER = NSStringFromClass([NSNumber class]).UTF8String;        // "NSArray"        CLASS_NAME_NSARRAY = NSStringFromClass([NSArray class]).UTF8String;    });        @try {        unsigned int outCount, i;        // 包含所有Property的数组        objc_property_t *properties = class_copyPropertyList([object class], &outCount);                // 遍历每个Property        for (i = 0; i < outCount; i++) {            // 取出对应Property            objc_property_t property = properties[i];            // 获取Property对应的变量名            NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];            // 获取Property的类型名            const char *propertyTypeName = getPropertyType(property);            // 获取Property的值            id propertyValue = [object valueForKey:propertyName];                        // 值为空,才设置默认值            if (!propertyValue) {                // NSString                if (strncmp(CLASS_NAME_NSSTRING, propertyTypeName, strlen(CLASS_NAME_NSSTRING)) == 0) {                    [object setValue:@"" forKey:propertyName];                }                                // NSNumber                if (strncmp(CLASS_NAME_NSNUMBER, propertyTypeName, strlen(CLASS_NAME_NSNUMBER)) == 0) {                    [object setValue:@0 forKey:propertyName];                }                                // NSArray                if (strncmp(CLASS_NAME_NSARRAY, propertyTypeName, strlen(CLASS_NAME_NSARRAY)) == 0) {                    [object setValue:@[] forKey:propertyName];                }            }        }                // 别忘了释放数组        free(properties);    } @catch (NSException *exception) {        NSLog(@"Check Entity Exception: %@", [exception description]);    }}

重点 - 解析property_getAttributes函数的结果

在整个处理过程中,property_getAttributes函数是关键,因为我们要首先确定Property的类型,才能根据类型赋初值,但是property_getAttributes函数返回的字符串比较“晦涩难懂”:

如下定义的Property: 

@property (copy, nonatomic) NSString *name;@property (strong, nonatomic) NSNumber *number;@property (strong, nonatomic) NSArray *array;@property (assign, nonatomic) NSInteger i;@property (assign, nonatomic) CGFloat f;@property (assign, nonatomic) char *cStr;

依次通过property_getAttributes获取的结果是: 

T@"NSString",C,N,V_nameT@"NSNumber",&,N,V_numberT@"NSArray",&,N,V_arrayTq,N,V_iTd,N,V_fT*,N,V_cStr

参考  Declared Properties of Objective-C Runtime Programming Guide

我们大概可以知道,T表示Type,后面跟着@表示Cocoa对象类型,后面的表示Property的属性,如Copy、strong等,然后就是变量名。

所以getPropertyType函数的工作就是纯粹的解析字符串,获取T@后面的类型名。

效果

例如我们有如下对象: 

@interface UserEntity : NSObject@property (copy, nonatomic) NSString *name;@property (strong, nonatomic) NSNumber *number;@property (strong, nonatomic) NSArray *array;@end

设置默认值: 

UserEntity *userEntity = [UserEntity new];// 检查属性,设置默认值。checkEntity(userEntity);// 使用...NSLog(@"name: %@", userEntity.name);NSLog(@"number: %@", userEntity.number);NSLog(@"array: %@", userEntity.array);

输出: 

2015-07-11 18:17:25.918 Common[6939:270543] name: 2015-07-11 18:17:25.918 Common[6939:270543] number: 02015-07-11 18:17:25.918 Common[6939:270543] array: ()

这样,一个对象的所有Property都有了初值。

总结

上面的例子只是个粗略的版本,只是检查了字符串、数字、数组,其实完全可以扩展出很多功能,如针对不同的类型,根据对象的类型,设置不同的默认初值等,靠读者你了~

Runtime是个好东西,但是别乱用啊=。=


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
2015 Objective
10 把从网络接口拿到的数据,转成模型。
iOS NSMutableAttributedString/NSAttributedString 富文本设置
iOS开发之蓝牙通信
Objc Runtime 学习笔记
Foundation Kit快速教程
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服