惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
博客园 - 聂微东
GbyAI
GbyAI
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园_首页
D
Docker
S
Security @ Cisco Blogs
K
Kaspersky official blog
爱范儿
爱范儿
Simon Willison's Weblog
Simon Willison's Weblog
TaoSecurity Blog
TaoSecurity Blog
V
V2EX
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Troy Hunt's Blog
Cloudbric
Cloudbric
博客园 - 三生石上(FineUI控件)
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The Hacker News
The Hacker News
美团技术团队
S
SegmentFault 最新的问题
L
Lohrmann on Cybersecurity
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
宝玉的分享
宝玉的分享
The Last Watchdog
The Last Watchdog
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Martin Fowler
Martin Fowler
Google Online Security Blog
Google Online Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tor Project blog
Vercel News
Vercel News
The Cloudflare Blog
G
Google Developers Blog
T
Threat Research - Cisco Blogs
AI
AI
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
Scott Helme
Scott Helme
S
Schneier on Security
大猫的无限游戏
大猫的无限游戏
The GitHub Blog
The GitHub Blog
S
Securelist
IT之家
IT之家
Microsoft Azure Blog
Microsoft Azure Blog

土土哥的Blog

好久没有更新博客=。= 阿里巴巴国际无线技术部 - 招人啦~求iOS、Android、Java 转-阿里巴巴国际无线技术部 - 在这里遇见最好的自己 反编译分析并模拟实现methodSignatureForSelector方法 反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash 有趣的Autolayout示例5-Masonry实现 在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper 用QuartzCode快速实现一个收藏动画 开源项目-拼图验证控件TTGPuzzleVerify的实现 Swift开源Mac App - BingWallPaper 有趣的Autolayout示例4-Masonry实现 翻译-为什么objc_msgSend必须用汇编实现 API返回结果设计经验与总结 结合访问Out Parameters出现EXC_BAD_ACCESS的例子,反编译汇编解读__autoreleasing 总结一些iOS项目中组织代码的方法 对组件化与模块化的思考与总结 开源项目-TTGTagCollectionView 有趣的Autolayout示例3-Masonry实现 Swift开源项目: TTGEmojiRate的实现 Swift写的库-TTGSnackbar 有趣的Autolayout示例2-Masonry实现 解决iOS项目的版本兼容问题-结合宏、Category和Runtime 有趣的Autolayout示例-Masonry实现 UITextView编辑时插入自定义表情-续-自定义表情图片的大小 RPC框架Thrift例子-PHP调用C++后端程序 GCD使用经验与技巧浅谈 为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权 Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5 @autoreleasepool-内存的分配与释放 有关宏定义的经验与技巧-简化代码-增强Log Effective-Objective-C-读书笔记-Item-4-如何正确定义常量 UITextView编辑时插入自定义表情-简单的图文混编 关于评论不见了=。= Entity和Model的不同-关于代码的数据层 一次审核被拒的经历-关于iCloud到底应该备份什么数据 Block类型变量-缓存Http请求与回调 提升UITableView性能-复杂页面的优化 NSString的Copy与内存分配 利用NSProxy实现消息转发-模块化的网络接口层设计-原创 Effective-Objective-C-读书笔记-Item-3 Effective-Objective-C-读书笔记-Item-2 Effective-Objective-C-读书笔记-Item-1 iOS项目的目录结构-原创 Android开源库-LinkTextView-原创 第一篇Blog
用Runtime的手段填充任意NSObject对象的nil属性
土土哥 · 2015-07-08 · via 土土哥的Blog

前言

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

前段时间参加了一下我们华科联创的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的函数:

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是个好东西,但是别乱用啊=。=

参考