背景1:是否有这样一种感受,你的应用会崩溃,查看崩溃日志或者调试发现后台返回的数据字段里面有null,这个null居然还是导致应用崩溃的“元凶”。
背景2:服务器返回的字段名和不一致,如果model属性少,你可以一个个对应赋值,多了肯定都会想到用运行时机制来赋值:
[self setValue:temDic[key] forKeyPath:key],这时候需要对两个地方的字段做映射。
思路:1、先判断是否需要映射 2、把服务器返回的数据赋给model 3、model属性null检查(不同类型赋不同值,并不是把所有null都赋@"")4、这个写法应该适应所有自定义的类型,所有给NSObject 加一个Category。
#import "NSObject+runtime.h"
@implementation NSObject (runtime)
- (void)fetchValueFormNetDict:(NSDictionary *)dic andMapDic:(NSDictionary *)MapDic{
NSArray *properties = [self getAllProperties];
//需要映射
if (MapDic != nil) {
NSArray *allKeys = dic.allKeys;
NSMutableDictionary *temDic = [NSMutableDictionary dictionaryWithCapacity:allKeys.count];
for (int i = 0; i < allKeys.count; i ++) {
NSString *key = allKeys[i];
[temDic setObject:dic[key] forKey:MapDic[key]];
}
// 遍历属性数组
for (NSString *key in properties) {
// 判断字典中是否包含这个key
[self setValue:temDic[key] forKeyPath:key];
}
}
else//不需要映射
{
// 遍历属性数组
for (NSString *key in properties) {
// 判断字典中是否包含这个key
[self setValue:dic[key] forKeyPath:key];
}
}
//null 或者 nil 处理
[self nullDeal];
}
//获取所有的属性名
- (NSArray *)getAllProperties
{
u_int count;
objc_property_t *properties =class_copyPropertyList([self class], &count);
NSMutableArray *propertiesArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i<count; i++)
{
const char* propertyName =property_getName(properties[i]);
[propertiesArray addObject: [NSString stringWithUTF8String: propertyName]];
}
free(properties);
return propertiesArray;
}
-(void)nullDeal
{
//找到属性对应的类型
NSDictionary *typeDic = [self classPropsFor:[self class]];
NSArray *arr = typeDic.allKeys; //所有属性名字
for (int i = 0; i < arr.count; i++) {
//属性类型名字(字符串格式)
NSString *typeString = typeDic[arr[i]];
if ([[self valueForKey:arr[i]] isKindOfClass:[NSNull class]] || [self valueForKey:arr[i]] == nil) {
if ([typeString isEqualToString:@"NSArray"]) {
[self setValue:@[] forKey:arr[i]];
}
if ([typeString isEqualToString:@"NSString"]) {
[self setValue:@"" forKey:arr[i]];
}
if ([typeString isEqualToString:@"NSNumber"]) {
[self setValue:@0 forKey:arr[i]];
}
if ([typeString isEqualToString:@"NSDictionary"]) {
[self setValue:@{} forKey:arr[i]];
}
if ([typeString isEqualToString:@"B"]) {
[self setValue:0 forKey:arr[i]];
}
if ([typeString isEqualToString:@"f"]) {
[self setValue:0 forKey:arr[i]];
}
if ([typeString isEqualToString:@"d"]) {
[self setValue:0 forKey:arr[i]];
}
if ([typeString isEqualToString:@"i"]) {
[self setValue:0 forKey:arr[i]];
}
}
}
}
//获取属性名称数组
- (NSDictionary *)classPropsFor:(Class)klass
{
if (klass == NULL) {
return nil;
}
NSMutableDictionary *results = [[NSMutableDictionary alloc] init];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(klass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if(propName) {
const char *propType = getPropertyType(property);
NSString *propertyName = [NSString stringWithUTF8String:propName];
NSString *propertyType = [NSString stringWithUTF8String:propType];
NSLog(@"propertyName %@ propertyType %@", propertyName, propertyType);
[results setObject:propertyType forKey:propertyName];
}
}
free(properties);
// returning a copy here to make sure the dictionary is immutable
return [NSDictionary dictionaryWithDictionary:results];
}
//获取属性类型的方法 c语言写法 T@"NSString",C,N,V_name Tf,N,V__float
static const char *getPropertyType(objc_property_t property) {
const char *attributes = property_getAttributes(property);
printf("attributes=%s\n", attributes);
char buffer[1 + strlen(attributes)];//多一个结束符号
strcpy(buffer, attributes);
char *state = buffer, *attribute;
while ((attribute = strsep(&state, ",")) != NULL) {
if (attribute[0] == 'T' && attribute[1] != '@') {
NSString *name = [[NSString alloc] initWithBytes:attribute + 1 length:strlen(attribute) - 1 encoding:NSASCIIStringEncoding];
return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
}
else if (attribute[0] == 'T' && attribute[1] == '@' && strlen(attribute) == 2) {
// it's an ObjC id type:
return "id";
}
else if (attribute[0] == 'T' && attribute[1] == '@') {
// it's another ObjC object type:
NSString *name = [[NSString alloc] initWithBytes:attribute + 3 length:strlen(attribute) - 4 encoding:NSASCIIStringEncoding];
return (const char *)[name cStringUsingEncoding:NSASCIIStringEncoding];
}
}
return "";
}
@end
基本工作差不多完了,现在来自定义一个类型试试:
#import <Foundation/Foundation.h>
@interface UserModel : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *home;
@property(nonatomic,strong)NSNumber *old;
@property(nonatomic,strong)NSArray *arr;
@property(nonatomic,strong)NSDictionary *dic;
@property(nonatomic,assign)BOOL isBoll;
-(instancetype)initWithDic:(NSDictionary *)dic;
@end
#import "UserModel.h"
#import "NSObject+runtime.h"
@implementation UserModel
-(instancetype)initWithDic:(NSDictionary *)dic
{
if (self == [super init]) {
// dic 网络数据 mapDic 映射之后的字典
[self fetchValueFormNetDict:dic andMapDic:[self mapDic]];
}
return self;
}
设置 映射字典格式:@"网络数据字段名":@"model属性名"
//如果服务器返回的字段 和 model的属性名字不一致 则需要映射 返回映射字典 否则返回nil表示不需要映射
-(NSDictionary *)mapDic
{
return @{@"wzcName":@"name",@"wzcAddress":@"home",@"isbol":@"isBoll"};
}
@end
实列化一个UserModel 对象
NSDictionary *data1 = @{@"wzcName":[NSNull null],@"wzcAddress":@"杭州市西湖区",@"isbol":@1};
UserModel *model1 = [[UserModel alloc]initWithDic:data1];
结果如下:
这样在后面数据操作不会崩溃了。