前言
好久没有写东西了,忙啊。
前段时间参加了一下我们华科联创的HackDay(本人在读研=。=,目前在阿里实习),作品是一款实时在线对战游戏 - 波波攒,(介绍请看知乎)
从iOS游戏客户端(用的SpriteKit)到后台(PHP CI + Node + SocketIO + MySQL)全是自己一个人倒腾出来的,做了一把真正的全栈工程师,爽啊~
后面会完善整个游戏,增加角色、优化啥的,过上一段时间会上线的哈~
回到正文,本文主要介绍了怎么用Runtime的手段遍历任意NSObject对象的所有property,检查其值是否是nil,是的话根据其类型为其填充一个默认值。
Runtime毕竟是个“危险”的技术,本文的代码只是个初步的尝试。
初衷
在做项目的过程中,总是会写一大堆if、else语句去检查对象的Property是否是nil,如从服务器返回的JSON中缺少属性,导致Entity的某些值为空;或者创建的对象没有对所有属性做初始化等等。写多了觉得好烦啊=。=
所以想到本文的方法,嗯,程序员总是懒的。
解决步骤
- 遍历一个对象的所有属性(默认不包括父类属性)。
- 判断属性是否是nil。
- 为nil的属性,获取它的类型。
- 根据类型设置初始值(如NSString可以设为空字符串;NSNumber可以设为@0)
Runtime
OC是一门“动态”、“基于消息”的语言,而Runtime就是利用OC的动态特性,在运行时对程序做出“调整”的技术。有关Runtime的官方文档、网上的资料很多,大家自学哈~
本文主要用了如下几个Runtime的函数:
1 2 3 4 5 6
| // 获取类的所有Property 1. 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)
|
示例
不好一块一块拆开说,直接上代码:
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
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] != '@') { return (const char *) [[NSData dataWithBytes:(attribute + 1) length:strlen(attribute) - 1] bytes]; } 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, ^{ CLASS_NAME_NSSTRING = NSStringFromClass([NSString class]).UTF8String; CLASS_NAME_NSNUMBER = NSStringFromClass([NSNumber class]).UTF8String; CLASS_NAME_NSARRAY = NSStringFromClass([NSArray class]).UTF8String; });
@try { unsigned int outCount, i; objc_property_t *properties = class_copyPropertyList([object class], &outCount);
for (i = 0; i < outCount; i++) { objc_property_t property = properties[i]; NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)]; const char *propertyTypeName = getPropertyType(property); id propertyValue = [object valueForKey:propertyName];
if (!propertyValue) { if (strncmp(CLASS_NAME_NSSTRING, propertyTypeName, strlen(CLASS_NAME_NSSTRING)) == 0) { [object setValue:@"" forKey:propertyName]; }
if (strncmp(CLASS_NAME_NSNUMBER, propertyTypeName, strlen(CLASS_NAME_NSNUMBER)) == 0) { [object setValue:@0 forKey:propertyName]; }
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:
1 2 3 4 5 6
| @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获取的结果是:
1 2 3 4 5 6
| T@"NSString",C,N,V_name T@"NSNumber",&,N,V_number T@"NSArray",&,N,V_array Tq,N,V_i Td,N,V_f T*,N,V_cStr
|
参考 Declared Properties of Objective-C Runtime Programming Guide
我们大概可以知道,T表示Type,后面跟着@表示Cocoa对象类型,后面的表示Property的属性,如Copy、strong等,然后就是变量名。
所以getPropertyType函数的工作就是纯粹的解析字符串,获取T@后面的类型名。
效果
例如我们有如下对象:
1 2 3 4 5
| @interface UserEntity : NSObject @property (copy, nonatomic) NSString *name; @property (strong, nonatomic) NSNumber *number; @property (strong, nonatomic) NSArray *array; @end
|
设置默认值:
1 2 3 4 5 6 7
| UserEntity *userEntity = [UserEntity new];
checkEntity(userEntity);
NSLog(@"name: %@", userEntity.name); NSLog(@"number: %@", userEntity.number); NSLog(@"array: %@", userEntity.array);
|
输出:
1 2 3 4
| 2015-07-11 18:17:25.918 Common[6939:270543] name: 2015-07-11 18:17:25.918 Common[6939:270543] number: 0 2015-07-11 18:17:25.918 Common[6939:270543] array: ( )
|
这样,一个对象的所有Property都有了初值。
总结
上面的例子只是个粗略的版本,只是检查了字符串、数字、数组,其实完全可以扩展出很多功能,如针对不同的类型,根据对象的类型,设置不同的默认初值等,靠读者你了~
Runtime是个好东西,但是别乱用啊=。=
参考
上一篇:解决iOS项目的版本兼容问题-结合宏、Category和Runtime
下一篇:有趣的Autolayout示例-Masonry实现