OC Runtime 修改YYModel nil变空字符处理

YYModel 没有好的 如果变量为nil 替换一个默认变量,OC 中 NSString 经常容易nil 崩溃, 所以有了此次需求,
注: 有一定的性能损失, 但是无需求的话不实现替换方法,性能损失应该很小, 有需求,用性能换稳定应该也划算

NSObject+YYMolde.h 新增

1
2
3
4
5
6
/**
转换完成后执行, 老变量新变量的替换
(只遍历当前类, 忽略父类)
@return <#return value description#>
*/
- (id)oldValueToNewValue:(id)value classTypeName:(const char *)classTypeName;

.m 新增

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
/**
* 解析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 "";
}

检查函数, 在合适的位置调用(model 转换完成后调用)

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
- (void)checkObj:(NSObject *)obj{
if ([obj respondsToSelector:@selector(oldValueToNewValue:classTypeName:)]){
@try{
unsigned int outCount, i;
//objc_property_t 数组获取
objc_property_t *properties = class_copyPropertyList([obj class], &outCount);
for (i = 0;i<outCount; i++){
// 单一的 objc_property_t
objc_property_t property = properties[i];
// 变量名获取
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
// 获取类型名称
const char * propertyTypeName = getPropertyType(property);
// 获取 property 值
id propertyValue = [obj valueForKey:propertyName];
id newValue = [(id<YYModel>)obj oldValueToNewValue:propertyValue classTypeName:propertyTypeName];
if (newValue != propertyValue){
[obj setValue:newValue forKey:propertyName];
}

}

free(properties);
// 如需遍历父类, 但是不需要的
// NSObject * subObj = obj.superclass;
// if (subObj){
//// NSLog(@"currentObj %@, subObjClassName---%@",[obj classForCoder],[subObj classForCoder]);
// [self checkObj:subObj];
// }



}@catch(NSException *exp){

}
}
}

然后自己的实现model 中 实现, 其他类型判断都可行

1
2
3
4
5
6
7
8
9
- (id)oldValueToNewValue:(id)value classTypeName:(const char *)classTypeName{
const char * className = NSStringFromClass([NSString class]).UTF8String;
if (strncmp(className, classTypeName, strlen(className)) == 0){
if(value == nil || value == NULL){
return @"";
}
}
return value;
}

重点 - 解析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@后面的类型名。

参考

git 地址