参考
背景介绍
我们知道,一个自定义对象要归档的时候,需要让对象实现NSCoding协议,对象内的每一个归档属性做一些encode和decode操作。归档的对象需要根据自身的属性写代码,耦合性太高。而且归档的属性一多,那写起来可就痛苦多了。
实际上,MJExtension框架上已经有了很好的解决方式:使用运行时的方式,获取对象内的所有属性,遍历归档,也可以设置排除掉不需要归档的属性(设置归档属性白名单/黑名单)。还可以将其抽取成一句宏,使用的时候就可以一句代码搞定!方便又解耦
常规的对象归档
这里为了文章的完整性,提供一下常规的对象归档做法。可以看到,需要实现NSCoding
协议的两个方法initWithCoder
和encodeWithCoder
,在其中对属性逐个进行encode
和decode
操作。代码如下:
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+WXXCoding
,decode
和encode
换了方法名,才不会覆盖系统原来的方法,具体实现:
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
协议的两个方法,但是对于需要归档的对象来说,initWithCoder
和encodeWithCoder
的代码是重复的,因此我们可以把这两个方法抽取成宏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
继承自XXX
,XXX
继承自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
中:
- 黑白名单的属性不需要加下划线,因为多了很多封装,属性也是重新封装过的。
- 背白名单的设置方法,对外提供了两种方式。
- 对于父类的遍历,也是不止对
NSObject
做判断,还有NSManagedObject
,甚至:NSURL
、NSDate
、NSValue
、NSData
、NSError
、NSArray
、NSDictionary
、NSString
、NSAttributedString
所以,我们的封装,只是简单的封装,简单的使用还是可以的,实际使用的话当然是MJExtension
比较全面!所以对于MJExtension
的其他的具体细节,下一篇我们继续吧。
网友评论