×

第三篇:runtime的实际应用场景——复杂对象一键归档、反归档

96
意一yiyi
2017.08.16 16:55* 字数 807

目录

使用runtime完成复杂对象一键归档、反归档

本篇主要讲解runtime的实际应用场景:复杂对象一键归档、反归档。是对成员变量和属性应用的一个例子。


使用runtime完成复杂对象一键归档、反归档


在数据持久化系列NSUserDefaults的文章中,我们已经实现了复杂对象的持久化,并且简单的提到了其中一个可以优化的地方,现在我们再来看一下那篇文章中所提到的:

可以优化的地方
1、给NSUserDefaults添加一个分类,为它扩展一对存取复杂对象的方法,这样就省得每次持久化复杂对象时都需要在外面写一遍那么多归档和反归档代码
#import <Foundation/Foundation.h>

@interface NSUserDefaults (SaveComplexObject)

- (void)yy_setComplexObject:(id)value forKey:(NSString *)defaultName;
- (id)yy_complexObjectForKey:(NSString *)defaultName;

@end


#import "NSUserDefaults+SaveComplexObject.h"

@implementation NSUserDefaults (SaveComplexObject)

- (void)yy_setComplexObject:(id)value forKey:(NSString *)defaultName {

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
        NSData *writeData = [NSKeyedArchiver archivedDataWithRootObject:value];
#pragma clang diagnostic pop

    [NS_User_Defaults setObject:writeData forKey:defaultName];
}

- (id)yy_complexObjectForKey:(NSString *)defaultName {

    NSData *readData = [NS_User_Defaults objectForKey:defaultName];
    
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
    return [NSKeyedUnarchiver unarchiveObjectWithData:readData];
#pragma clang diagnostic pop
}

@end
2、为NSObject根类扩展一下NSCoding协议并实现协议里的encodeWithCoder:initWithCoder:方法,而且在协议方法的实现我们还要用runtime获取类的属性列表来做encode和decode

实际开发中往往我们会创建很多复杂对象(即model),而且每个model里往往有很多不同的属性,那如果我们要分别对这些对象做持久化的话,岂不是要在每个model的类里都遵循一下NSCoding协议并实现encodeWithCoder:initWithCoder:方法了嘛,更要命的是在两个方法实现的时候我们还要对model的一大片的属性分别进行encode和decode操作,这工作量可以说是相当大了。

  • 那么针对岂不是要在每个model的类里都遵循一下NSCoding协议并实现encodeWithCoder:initWithCoder:方法了嘛这个问题,我们可以想到既然每个model的类都要实现相同的方法,而且方法的实现部分还有某种可以抽离出来的共性,那索性就不实现了呗,让所有类的父类来实现不就好了嘛

  • 针对更要命的是在两个方法实现的时候我们还要对model的一大片的属性分别进行encode和decode操作,我们可以想到既然所有的属性都要参与encode和decode操作,那我们可以想办法获取属性列表啊,然后遍历,让代码自己对属性执行encode和decode操作不就好了嘛。那怎么能获取到属性列表呢?runtime的函数就可以获取啊!

因此,我们为父类NSObject添加了一个ArchiveAndUnarchive的分类来让它遵循NSCoding协议并实现encodeWithCoder:initWithCoder:两个方法,同时在encodeWithCoder:initWithCoder:方法的实现部分我们通过runtime获取成员变量列表(属性列表是成员变量列表的一个子集)的函数来获取所有的成员变量,让代码自己执行encode和decode操作。如下:

#import <Foundation/Foundation.h>

@interface NSObject (ArchiveAndUnarchive)

@end


#import "NSObject+YY__ArchiveAndUnarchive.h"
#import <objc/runtime.h>

@implementation NSObject (YY__ArchiveAndUnarchive)

- (void)encodeWithCoder:(NSCoder *)aCoder {
    
    // 记录一个类成员变量的个数
    unsigned int ivarCount = 0;
    // 获取一个类的成员变量列表
    Ivar *ivars = class_copyIvarList([self class], &ivarCount);
    
    for (int i = 0; i < ivarCount; i ++) {
        
        // 获取单个成员变量
        Ivar ivar = ivars[I];
        
        // 获取成员变量的名字并将其转换为OC字符串
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        
        // 获取该成员变量对应的值
        id value = [self valueForKey:key];
        
        // encode
        [aCoder encodeObject:value forKey:key];
    }
    
    // 释放ivars
    free(ivars);
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    
    // 因为没有superClass了
    self = [self init];
    
    if (self != nil) {
        
        unsigned int ivarCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &ivarCount);
        for (int i = 0; i < ivarCount; i ++) {
            
            Ivar ivar = ivars[I];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            
            // decode
            id value = [aDecoder decodeObjectForKey:key];
            
            // 赋值
            [self setValue:value forKey:key];
        }
        
        free(ivars);
    }
    
    return self;
}

@end

完成,这样我们在开发中只要把这个分类拖进项目中,就可以完成复杂对象一键归档、反归档了。

iOS 开发:runtime
Web note ad 1