Runtime系列10之-Runtime的应用(三)

利用runtime 获取所有属性来重写归档解档方法

  1. 我们先来看看基本的归档方法:
  2. 假设现在有一个Movie类,有3个属性,它的.h.m文件这这样的:

     //Movie.h文件
     #import <Foundation/Foundation.h>
     //1. 如果想要当前类可以实现归档与反归档,需要遵守一个协议NSCoding
     @interface Movie : NSObject<NSCoding>
     @property (nonatomic, copy) NSString *movieId;
     @property (nonatomic, copy) NSString *movieName;
     @property (nonatomic, copy) NSString *pic_url;
     @end
        
     //Movie.m文件
     #import "Movie.h"
        
     @implementation Movie
     //必须实现这个方法,用于归档
     - (void)encodeWithCoder:(NSCoder *)aCoder
     {
         [aCoder encodeObject:_movieId forKey:@"id"];
         [aCoder encodeObject:_movieName forKey:@"name"];
         [aCoder encodeObject:_pic_url forKey:@"url"];
     }
     //必须实现这个方法用于解档
     - (id)initWithCoder:(NSCoder *)aDecoder
     {
         if (self = [super init]) {
             self.movieId = [aDecoder decodeObjectForKey:@"id"];
             self.movieName = [aDecoder decodeObjectForKey:@"name"];
             self.pic_url = [aDecoder decodeObjectForKey:@"url"];
         }
         return self;
     }
     @end
    
  3. 如果这里有100个属性,那么我们也只能把100个属性都给写一遍。
  4. 不过你会使用runtime后,这里就有更简便的方法。
  5. 下面看看runtime的实现方式:

     #import "Movie.h"
     #import <objc/runtime.h>
     @implementation Movie
     - (void)encodeWithCoder:(NSCoder *)encoder
     {
         unsigned int count = 0;
         Ivar *ivars = class_copyIvarList([Movie class], &count);
         for (int i = 0; i<count; i++) {
             // 取出i位置对应的成员变量
             Ivar ivar = ivars[i];
             // 查看成员变量
             const char *name = ivar_getName(ivar);
             // 归档
             NSString *key = [NSString stringWithUTF8String:name];
             id value = [self valueForKey:key];
             [encoder encodeObject:value forKey:key];
         }
         free(ivars);
     }
     - (id)initWithCoder:(NSCoder *)decoder
     {
         if (self = [super init]) {
             unsigned int count = 0;
             Ivar *ivars = class_copyIvarList([Movie class], &count);
             for (int i = 0; i<count; i++) {
                 // 取出i位置对应的成员变量
                 Ivar ivar = ivars[i];
                 // 查看成员变量
                 const char *name = ivar_getName(ivar);
                 // 归档
                 NSString *key = [NSString stringWithUTF8String:name];
                 id value = [decoder decodeObjectForKey:key];
                 // 设置到成员变量身上
                 [self setValue:value forKey:key];
             }
             free(ivars);
         }
         return self;
     }
     @end
    
  6. 这样的方式实现,不管有多少个属性,写这几行代码就搞定了。怎么,还嫌麻烦,下面看看更加简便的方法:两句代码搞定。
  7. 我们把encodeWithCoderinitWithCoder这两个方法抽成宏

     #import "Movie.h"
     #import <objc/runtime.h>
        
     #define encodeRuntime(A) \
     \
     unsigned int count = 0;\
     Ivar *ivars = class_copyIvarList([A class], &count);\
     for (int i = 0; i<count; i++) {\
     Ivar ivar = ivars[i];\
     const char *name = ivar_getName(ivar);\
     NSString *key = [NSString stringWithUTF8String:name];\
     id value = [self valueForKey:key];\
     [encoder encodeObject:value forKey:key];\
     }\
     free(ivars);\
     \
        
     #define initCoderRuntime(A) \
     \
     if (self = [super init]) {\
     unsigned int count = 0;\
     Ivar *ivars = class_copyIvarList([A class], &count);\
     for (int i = 0; i<count; i++) {\
     Ivar ivar = ivars[i];\
     const char *name = ivar_getName(ivar);\
     NSString *key = [NSString stringWithUTF8String:name];\
     id value = [decoder decodeObjectForKey:key];\
     [self setValue:value forKey:key];\
     }\
     free(ivars);\
     }\
     return self;\
     \
        
     @implementation Movie
        
     - (void)encodeWithCoder:(NSCoder *)encoder
        
     {
         encodeRuntime(Movie)
     }
        
     - (id)initWithCoder:(NSCoder *)decoder
     {
         initCoderRuntime(Movie)
     }
     @end
    

    我们可以把这两个宏单独放到一个文件里面,这里以后需要进行数据持久化的模型都可以直接使用这两个宏。

例2:

 // NSObject+Extension.h文件
 #import <Foundation/Foundation.h>
@interface NSObject (Extension)
- (NSArray *)ignoredNames;
- (void)encode:(NSCoder *)aCoder;
- (void)decode:(NSCoder *)aDecoder;
@end
 // NSObject+Extension.m文件
 #import "NSObject+Extension.h"
#import <objc/runtime.h>
@implementation NSObject (Extension)
- (void)decode:(NSCoder *)aDecoder {
    // 一层层父类往上查找,对父类的属性执行归解档方法
    Class c = self.class;
    while (c &&c != [NSObject class]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList(c, &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 如果有实现该方法再去调用
            if ([self respondsToSelector:@selector(ignoredNames)]) {
                if ([[self ignoredNames] containsObject:key]) continue;
            }
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }
}
- (void)encode:(NSCoder *)aCoder {
    // 一层层父类往上查找,对父类的属性执行归解档方法
    Class c = self.class;
    while (c &&c != [NSObject class]) {
        unsigned int outCount = 0;
        Ivar *ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            // 如果有实现该方法再去调用
            if ([self respondsToSelector:@selector(ignoredNames)]) {
                if ([[self ignoredNames] containsObject:key]) continue;
            }
            id value = [self valueForKeyPath:key];
            [aCoder encodeObject:value forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }
}
@end

上面分类使用方法,在需要归解档的对象中实现下面方法即可:

// 设置需要忽略的属性
- (NSArray *)ignoredNames {
    return @[@"bone"];
}
// 在系统方法内来调用我们的方法
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        [self decode:aDecoder];
    }
    return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [self encode:aCoder];
}

这样看来,我们每次又要写同样的代码,我们可以将归解档两个方法封装为宏,在需要的地方一句宏搞定,如果有不需要归解档的属性就实现ignoredNames 方法

Table of Contents