iOS相关

runtime:一句代码实现对象NSCoding

2017-12-14  本文已影响2人  上发条的树

参考

背景介绍

我们知道,一个自定义对象要归档的时候,需要让对象实现NSCoding协议,对象内的每一个归档属性做一些encode和decode操作。归档的对象需要根据自身的属性写代码,耦合性太高。而且归档的属性一多,那写起来可就痛苦多了。
实际上,MJExtension框架上已经有了很好的解决方式:使用运行时的方式,获取对象内的所有属性,遍历归档,也可以设置排除掉不需要归档的属性(设置归档属性白名单/黑名单)。还可以将其抽取成一句宏,使用的时候就可以一句代码搞定!方便又解耦

常规的对象归档

这里为了文章的完整性,提供一下常规的对象归档做法。可以看到,需要实现NSCoding协议的两个方法initWithCoderencodeWithCoder,在其中对属性逐个进行encodedecode操作。代码如下:

WxxAccount.h

#import <Foundation/Foundation.h>

@interface WxxAccount : NSObject<NSCoding>

@property(nonatomic,assign)NSInteger CAR_ID;    //车辆ID
@property(nonatomic,assign) NSInteger UID;      //用户ID
@property(nonatomic,assign)NSInteger is_driver; //是否是司机 0否 1是
@property(nonatomic,copy)NSString *USER_NAME;   //用户手机号
@property(nonatomic,copy)NSString *session_id;  //session
@property(nonatomic,copy)NSString *nickName;    //昵称
@property(nonatomic,copy)NSString *PICURL;      //头像

@end

WxxAccount.m

-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if(self == [super init]){
        self.CAR_ID = [aDecoder decodeIntegerForKey:
                       @"CAR_ID"];
        self.UID = [aDecoder decodeIntegerForKey:@"UID"];
        self.is_driver = [aDecoder decodeIntegerForKey:@"is_driver"];
        self.USER_NAME = [aDecoder decodeObjectForKey:@"USER_NAME"];
        self.session_id = [aDecoder decodeObjectForKey:@"session_id"];
        self.nickName = [aDecoder decodeObjectForKey:@"nickName"];
        self.PICURL = [aDecoder decodeObjectForKey:@"PICURL"];
    }
    return self;
}

-(void) encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeInteger: self.CAR_ID forKey:@"CAR_ID"];
    [aCoder encodeInteger: self.UID forKey:@"UID"];
    [aCoder encodeInteger: self.is_driver forKey:@"is_driver"];
    [aCoder encodeObject:self.USER_NAME forKey:@"USER_NAME"];
    [aCoder encodeObject:self.session_id forKey:@"session_id"];
    [aCoder encodeObject:self.nickName forKey:@"nickName"];
    [aCoder encodeObject:self.PICURL forKey:@"PICURL"];
}

获取对象的所有属性

运行时真是一个神奇的存在,具体使用可以参考文章开头第一个链接。下面是获取WxxAccount对象内所有属性的方式:

#import "WxxAccount.h"
#import <objc/runtime.h>

    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([WxxAccount class], &count);
    //遍历获取所有属性
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        const char *name = ivar_getName(ivar);
        const char *type = ivar_getTypeEncoding(ivar);
        NSLog(@"成员变量:%s ------- 成员变量类型:%s",name,type);
    }
    //释放内存
    free(ivars);

运行结果:

成员变量:_CAR_ID ------- 成员变量类型:q
成员变量:_UID ------- 成员变量类型:q
成员变量:_is_driver ------- 成员变量类型:q
成员变量:_USER_NAME ------- 成员变量类型:@"NSString"
成员变量:_session_id ------- 成员变量类型:@"NSString"
成员变量:_nickName ------- 成员变量类型:@"NSString"
成员变量:_PICURL ------- 成员变量类型:@"NSString"

遍历对象归档

创建NSObject的分类NSObject+WXXCodingdecodeencode换了方法名,才不会覆盖系统原来的方法,具体实现:
NSObject+WXXCoding.h

#import <Foundation/Foundation.h>

@interface NSObject (WXXCoding)

-(NSArray*)ignoredProperties;//不需要归档的属性,在具体的主类中实现
-(void)wxx_decode:(NSCoder*)aDecoder;
-(void)wxx_encode:(NSCoder*)aCoder;

@end

NSObject+WXXCoding.m

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

@implementation NSObject (WXXCoding)

-(void)wxx_decode:(NSCoder*)aDecoder{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    //遍历获取所有属性
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        //有该方法实现,再排除无需归档的属性
        if ([self respondsToSelector:@selector(ignoredProperties)]) {
            //continue 是因为需要释放ivars
            if ([[self ignoredProperties]containsObject:key]) continue;
        }
        id value = [aDecoder decodeObjectForKey:key];
        [self setValue:value forKey:key];
    }
    //释放内存
    free(ivars);
}

-(void)wxx_encode:(NSCoder*)aCoder{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        if ([self respondsToSelector:@selector(ignoredProperties)]) {
            if ([[self ignoredProperties]containsObject:key]) continue;
        }
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
    free(ivars);
}

@end

此时,WxxAccount的写法就变成这样了:

WXXAcount.h

不变

WXXAcount.m

#import "WxxAccount.h"
#import "NSObject+WXXCoding.h"

@implementation WxxAccount

//假设PICURL这个属性不需要归档,注意加上下划线,因为系统自动生成订单实例变量会默认加下划线的。
-(NSArray*)ignoredProperties{
    return @[@"_PICURL"];
}

-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if(self == [super init]){
        [self wxx_decode:aDecoder];
    }
    return self;
}

-(void) encodeWithCoder:(NSCoder *)aCoder{
    [self wxx_encode:aCoder];
}

@end

是不是简洁不少!可是每次还是需要实现NSCoding协议的两个方法,但是对于需要归档的对象来说,initWithCoderencodeWithCoder的代码是重复的,因此我们可以把这两个方法抽取成宏WXXCodingImplementation,放到NSObject+WXXCoding.h

#import <Foundation/Foundation.h>

@interface NSObject (WXXCoding)

-(NSArray*)ignoredProperties;//不需要归档的属性,在具体的主类中实现
-(void)wxx_decode:(NSCoder*)aDecoder;
-(void)wxx_encode:(NSCoder*)aCoder;

#define WXXCodingImplementation \
-(instancetype)initWithCoder:(NSCoder *)aDecoder{\
if(self == [super init]){\
[self wxx_decode:aDecoder];\
}\
return self;\
}\
\
-(void) encodeWithCoder:(NSCoder *)aCoder{\
[self wxx_encode:aCoder];\
}

@end

此时,WXXAcount中的写法就相当简介了:
WXXAcount.h

不变

WXXAcount.m

#import "WxxAccount.h"
#import "NSObject+WXXCoding.h"

@implementation WxxAccount

WXXCodingImplementation

//假设PICURL这个属性不需要归档,注意加上下划线,因为系统自动生成订单实例变量会默认加下划线的。
-(NSArray*)ignoredProperties{
    return @[@"_PICURL"];
}

@end

关于继承,父类属性也要归档

当然,在我们的例子中,WXXAcount直接继承自NSObject,如果WXXAcount间接继承自NSObject,也就是XXX存放公共的属性,WXXAcount继承自XXXXXX继承自NSObject。那么我们的例子就不适用了,因为我们只针对当前类做属性遍历,而忽略了它的父类XXX,甚至更多层的继承。
具体的做法,应该是在遍历的时候,加一个循环,如果是NSObject就终止循环。

修改了NSObject+WXXCoding.m中的代码:

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

@implementation NSObject (WXXCoding)

-(void)wxx_decode:(NSCoder*)aDecoder{
    Class clazz = [self class];
    //循环遍历父类,知道父类为NSObject才停止,此处并不完美,具体看总结。
    while (clazz && clazz != [NSObject class]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(clazz, &count);
        //遍历获取所有属性
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            //有该方法实现,再排除无需归档的属性
            if ([self respondsToSelector:@selector(ignoredProperties)]) {
                //continue 是因为需要释放ivars
                if ([[self ignoredProperties]containsObject:key]) continue;
            }
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        //释放内存
        free(ivars);
        clazz = [clazz superclass];
    }

}

-(void)wxx_encode:(NSCoder*)aCoder{
    Class clazz = [self class];
    while (clazz && clazz != [NSObject class]) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList(clazz, &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            if ([self respondsToSelector:@selector(ignoredProperties)]) {
                if ([[self ignoredProperties]containsObject:key]) continue;
            }
            id value = [self valueForKey:key];
            [aCoder encodeObject:value forKey:key];
        }
        free(ivars);
        clazz = [clazz superclass];
    }
    
}

@end

总结

当然,我们可以发现MJExtension中:

所以,我们的封装,只是简单的封装,简单的使用还是可以的,实际使用的话当然是MJExtension比较全面!所以对于MJExtension的其他的具体细节,下一篇我们继续吧。

上一篇 下一篇

猜你喜欢

热点阅读