对象的copy和自动归档Archive操作

我们遇到的问题是什么?

在构建iOS的app过程中,我们经常会遇到的问题,对一个自定义对象model进行copy或者归档操作,所以我们必须实现nscopy协议和nscoding协议才能满足我们的需求,例如有个person对象如下:

#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCopy>
@property (nonatomic, copy) NSString *name;
@end

#import "Person.h"
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
     Person *p = [Person alloc] init];
     p.name = self.name;
     return p;
}
@end

我要实现NSCopy的方法 -(id)copyWithZone:(NSZone *)zone; 这时候我们就可以调用   Person *p2 = [p1 copy];来复制一个copy对象了,否则程序会崩溃(可自行尝试)。

再例如一个Student对象,我们要对他进行归档操作:

#import <Foundation/Foundation.h>
@interface Student : NSObject<NSCoding>
@property (nonatomic, copy) NSString *name;
@end

#import "Student.h"
@implementation Student
- (void)encodeWithCoder:(NSCoder *)aCoder  {
     [aCode encodeObject:self.name forKey:@"name"];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        self.name = [aDecoder decodeObjectForKey:@"name"];
    }
    return self;
}
@end

这个时候我们需要实现NSCoding协议的两个方法:

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

然后我们可以调用  

[NSKeyedArchiver archiveRootObject:student toFile:filePath];   //进行归档操作

Student *student = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; //从归档文件中读对象

我们可想而知,如果当这个自定义对象的属性非常多的时候,或者工程中有很多种这样的对象时,我们将会写很多类似的代码,这是我们不愿意看到,也不愿意去做的事情,我们应该主要精力放在项目的构建和业务处理上。

如何解决问题?

我们发现每个类实现这两个协议的时候代码都非常的相似,那么我们要做的就是将他们抽象出来,方便以后使用。

在做这件事之前还有一点要介绍的地方,项目中我们经常要做的事情就是对数据的解析,也就是将网络请求返回的json对象(比较常用)解析成自定义的对象model,例如上边的Person和Student(这两个类写的比较简单,实际我们定义的model要比这复杂的多)。这里我顺便也对这部分做了简单的封装处理。

进入主题

好,接下来正式进入主题。

创建了两个基类 JRFBaseModel和JRFArchiveBaseModel。其中JRFBaseModel对json的处理做了简单的封装,并实现了NSCopy协议,代码如下:

#import <Foundation/Foundation.h>

@interface JRFBaseModel : NSObject<NSCopying>
//对返回的json进行解析
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;

//以下方法是对json进行不同的解析返回
- (NSString *)strForKey:(NSString *)key;
- (NSArray *)arrForKey:(NSString *)key;
- (NSDictionary *)dictForKey:(NSString *)key;
- (NSNumber *)numberForKey:(NSString *)key;

//NSNumber类型属性值的复制操作
- (void)memcpy:(char *)agePtr value:(NSNumber *)value;
@end

对于json解析的封装,首先定义一个私有属性 resultDict 来存储传入的字典数据

@interface JRFBaseModel ()

@property (nonatomic, strong) NSDictionary *resultDict;

@end

@implementation JRFBaseModel

- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
    if (self = [super init]) {
        if (dictionary) {
            self.resultDict = [NSDictionary dictionaryWithDictionary:dictionary];
        } else {
            self.resultDict = [NSDictionary dictionary];
        }
    }
    return self;
}

然后是对字符串属性值的解析过程,数组和字典的实现类似,不做过多说明,详情可见 这里

- (NSString *)strForKey:(NSString *)key {
    if (key) {
        NSString *value = [self.resultDict objectForKey:key];
        if (value && ([value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]])) {
            return [NSString stringWithFormat:@"%@", value];
        } else {
            NSLog(@"key type error (not string or not number)");
        }
    }
    return @"";
}
……
……

NSCopy协议的方法实现如下

#define copy

- (id)copyWithZone:(NSZone *)zone {
    //根据类类型创建新类
    Class originClass = [self class];
    id newClass = [[originClass alloc] init];
    
    //遍历类的属性,获取属性名和属性值并给新类赋值
    unsigned int outCount, i;
    //取出所有属性
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for (i = 0; i < outCount; i++)
    {
        //获取属性名和属性值
        objc_property_t property = properties[i];
        const char* char_f = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        id propertyValue = [self valueForKey:(NSString *)propertyName];
        NSString *ivarName = [NSString stringWithFormat:@"_%@", propertyName];
        Ivar ivar = class_getInstanceVariable([self class], [ivarName UTF8String]);
        //如果属性是int,float,double,bool需要做特殊处理
        if ([propertyValue isKindOfClass:[NSNumber class]]) {
            NSLog(@"number");
            
            ptrdiff_t ageOffset = ivar_getOffset(ivar);
            char *agePtr = ((char *)(__bridge void *)newClass) + ageOffset;
            [self memcpy:agePtr value:propertyValue];
        } else {
        
            //通过属性名获取到变量,并给新类的该变量赋值
            object_setIvar(newClass, ivar, propertyValue);
        }
    }
    free(properties);
    return newClass;
}

- (void)memcpy:(char *)agePtr value:(NSNumber *)value {
    if (strcmp([value objCType], @encode(float)) == 0) {
        float v = [value floatValue];
        memcpy(agePtr, &v, sizeof(v));
    } else if (strcmp([value objCType], @encode(double)) == 0) {
        double v = [value doubleValue];
        memcpy(agePtr, &v, sizeof(v));
    } else if (strcmp([value objCType], @encode(int)) == 0) {
        int v = [value intValue];
        memcpy(agePtr, &v, sizeof(v));
    } else if (strcmp([value objCType], @encode(long)) == 0) {
        long v = [value longValue];
        memcpy(agePtr, &v, sizeof(v));
    } else if (strcmp([value objCType], @encode(long long)) == 0) {
        long long v = [value longLongValue];
        memcpy(agePtr, &v, sizeof(v));
    } else {
        int v = [value intValue];
        memcpy(agePtr, &v, sizeof(v));
    }
}
View Code

NSCoding协议的方法实现如下

- (void)encodeWithCoder:(NSCoder *)aCoder {
    //遍历该类的属性值
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList([self class], &outCount);
    for (i = 0; i < outCount; i++)
    {
        objc_property_t property = properties[i];
        const char* char_f = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        id propertyValue = [self valueForKey:(NSString *)propertyName];
        
        //根据属性值和属性名进行necode
        [aCoder encodeObject:propertyValue forKey:propertyName];
    }
    free(properties);
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        //遍历该类的属性值
        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList([self class], &outCount);
        for (i = 0; i < outCount; i++)
        {
            objc_property_t property = properties[i];
            const char* char_f = property_getName(property);
            NSString *propertyName = [NSString stringWithUTF8String:char_f];
            //decode取出属性值
            id propertyValue = [aDecoder decodeObjectForKey:propertyName];
            //给变量赋值
            NSString *ivarName = [NSString stringWithFormat:@"_%@", propertyName];
            Ivar ivar = class_getInstanceVariable([self class], [ivarName UTF8String]);
            //如果属性是int,float,double,bool需要做特殊处理
            if ([propertyValue isKindOfClass:[NSNumber class]]) {
                NSLog(@"number");
                
                ptrdiff_t ageOffset = ivar_getOffset(ivar);
                char *agePtr = ((char *)(__bridge void *)self) + ageOffset;
                [self memcpy:agePtr value:propertyValue];
            } else {
                
                //通过属性名获取到变量,并给新类的该变量赋值
                object_setIvar(self, ivar, propertyValue);
            }
            
        }
        free(properties);
    }
    return self;
}
View Code

协议实现中主要用到了iOS运行时机制,动态创建类、动态获取类的实例、以及属性名和属性值、动态的给属性赋值等,具体的参见代码中的注释。

有了上边的封装之后,我们在构建对象的时候,只需继承JRFBaseModel或者JRFArchiveBaseModel,之后就可以随心所遇的进行copy和归档了

NSDictionary *jsonDict = @{@"name": @"xiaoli",
                               @"introduction": @"希望你都好",
                               @"age": [NSNumber numberWithInt:20],
                               @"price": [NSNumber numberWithFloat:12.33],
                               @"bmale": [NSNumber numberWithBool:YES]};
    
    
    Person *p1 = [[Person alloc] initWithDictionary:jsonDict];
    [p1 toString];
    
    Person *p2 = [p1 copy];
    [p2 toString];
jsonDict = @{@"name": @"lilei",
                 @"classname": @"一班",
                 @"age": [NSNumber numberWithInt:18],
                 @"scrore": [NSNumber numberWithFloat:98.33],
                 @"bmale": [NSNumber numberWithBool:NO]};
    Student *s1 = [[Student alloc] initWithDictionary:jsonDict];
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent:@"student.archiver"];
    NSLog(@"%@", filePath);
    //归档操作
    [NSKeyedArchiver archiveRootObject:s1 toFile:filePath];
    //从归档文件中读取数据
    Student *s2 = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
    [s2 toString];

这里的toString方法只是简单的打印出了各个属性的值

 - (void)toString { NSLog(@"name: %@, classname: %@, age: %d, score: %f, bMale: %d", self.name, self.className, self.age, self.score, self.bMale); } 

具体的实现请参见github上的Demo  

https://github.com/appleboyaug/JRFBaseModelDemo

 

 

posted @ 2015-03-20 18:18  船长_jerry  阅读(252)  评论(0编辑  收藏  举报