对象的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协议的方法实现如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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)); } }
NSCoding协议的方法实现如下
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
- (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; }
协议实现中主要用到了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