好久没有写东西了,忙啊。
前段时间参加了一下我们华科联创的HackDay(本人在读研=。=,目前在阿里实习),作品是一款 实时在线对战游戏 - 波波攒 ,(介绍请看 知乎 )
从iOS游戏客户端(用的 SpriteKit )到后台( PHP CI + Node + SocketIO + MySQL )全是自己一个人倒腾出来的,做了一把真正的全栈工程师,爽啊~
后面会完善整个游戏,增加角色、优化啥的,过上一段时间会上线的哈~
回到正文,本文主要介绍了怎么用Runtime的手段遍历任意NSObject对象的所有property,检查其值是否是nil,是的话根据其类型为其填充一个默认值。
Runtime毕竟是个“危险”的技术,本文的代码只是个初步的尝试。
在做项目的过程中,总是会写一大堆if、else语句去检查对象的Property是否是nil,如从服务器返回的JSON中缺少属性,导致Entity的某些值为空;或者创建的对象没有对所有属性做初始化等等。写多了觉得好烦啊=。=所以想到本文的方法,嗯,程序员总是懒的。
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的类型,才能根据类型赋初值,但是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是个好东西,但是别乱用啊=。=
联系客服