利用Runtime对Ivar实例变量进行共用的归档和解档方式
一、介绍
在OC中每一个对象持有的变量都是实例变量,实例变量包括成员变量和属性变量,在runtime中用Ivar表示对象的实例变量。其实,runtime源码中可以看到,Ivar也是一个结构体(基本上在runtime中变量的声明都是用结构体实现的),如下所示,同时苹果为这个结构体另外定义了一个结构体指针。
//变量结构体 struct objc_ivar { //变量名称 char * _Nullable ivar_name OBJC2_UNAVAILABLE; //变量类型 char * _Nullable ivar_type OBJC2_UNAVAILABLE; //在地址中的偏移量 int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } OBJC2_UNAVAILABLE; //声明了一个变量结构体指针 typedef struct objc_ivar *Ivar;
二、函数
知道了它的数据结构后,我们再来看看runtime中都提供了哪些常用的关于Ivar的相关函数,如下部分所示:
//获取类的变量列表 Ivar _Nonnull * _Nullable class_copyIvarList(Class _Nullable cls, unsigned int * _Nullable outCount) ; //获取变量的名称 const char * _Nullable ivar_getName(Ivar _Nonnull v); //获取变量的类型 const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v) ; //通过变量获取对象 id _Nullable object_getIvar(id _Nullable obj, Ivar _Nonnull ivar); //设置新的变量 object_setIvar(id _Nullable obj, Ivar _Nonnull ivar, id _Nullable value);
三、应用
在前面的篇章中,介绍过了对property属性获取,并使用property属性可以进行字典与模型的互转。在对象的实例变量的范畴上来看,property明显是少于Ivar的,它只是Ivar的一部分,如果我们使用property进行属性的归档和解档时,会存在数据不完整的问题,此时使用Ivar再合适不过了。不管是公/私有属性、公/私有成员变量,使用Ivar的相关函数都可以拿到。归档和解归档的过程直接放在基类中实现,通过获取所有的ivar进行decodeObjectForKey和encodeObject即可,子类继承自该基类后,就默认全部实现了归档和解归档。实现步骤如下:
(1)定义基类BaseEntity,获取Ivar列表,对ivar进行归档和解档
// // BaseEntity.h // 运行时 // // Created by 夏远全 on 2019/11/11. // #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> NS_ASSUME_NONNULL_BEGIN @interface BaseEntity : NSObject @end NS_ASSUME_NONNULL_END
// // BaseEntity.m // 运行时 // // Created by 夏远全 on 2019/11/11. // #import "BaseEntity.h" #import <objc/runtime.h> @implementation BaseEntity //归档 - (void)encodeWithCoder:(NSCoder *)coder { NSArray *ivarNames = [self getAllIvarNames]; for (NSString *str_ivar_name in ivarNames) { //去掉前面的下划线"_" NSString *key = [str_ivar_name substringFromIndex:1]; //归档 [coder encodeObject:[self valueForKey:key] forKey:key]; } } //解档 - (instancetype)initWithCoder:(NSCoder *)coder { self = [super init]; if (self) { NSArray *ivarNames = [self getAllIvarNames]; for (NSString *str_ivar_name in ivarNames) { //去掉前面的下划线"_" NSString *key = [str_ivar_name substringFromIndex:1]; //解档 [self setValue:[coder decodeObjectForKey:key] forKey:key]; } } return self; } //获取类的所有实例变量 -(NSArray *)getAllIvarNames { //存储所有的变量名称 NSMutableArray *ivarNames = [NSMutableArray array]; //拷贝实例变量列表 unsigned int outCount; Ivar *ivars = class_copyIvarList([self class], &outCount); //遍历实例变量列表 for (int i=0; i<outCount; i++) { Ivar ivar = ivars[i]; const char *ivar_name = ivar_getName(ivar); NSString *str_ivar_name = [NSString stringWithUTF8String:ivar_name]; [ivarNames addObject:str_ivar_name]; } return ivarNames; } @end
(2)创建子类FileModel,进行验证
// // FileModel.h // 运行时 // // Created by 夏远全 on 2019/11/11. // #import "BaseEntity.h" NS_ASSUME_NONNULL_BEGIN @interface FileModel : BaseEntity -(instancetype)initWithFileType:(NSString *)fileType fileName:(NSString *)fileName fileSize:(CGFloat)fileSize; @end NS_ASSUME_NONNULL_END
// // FileModel.m // 运行时 // // Created by 夏远全 on 2019/11/11. // #import "FileModel.h" @interface FileModel () { NSString *_fileType; //文件类型,私有成员变量 } @property (nonatomic, copy) NSString *fileName; //文件名称,私有实例属性 @property (nonatomic, assign) CGFloat fileSize; //文件大小,私有实例属性 @end @implementation FileModel
//初始化 -(instancetype)initWithFileType:(NSString *)fileType fileName:(NSString *)fileName fileSize:(CGFloat)fileSize { self = [super init]; if (self) { _fileType = fileType; _fileName = fileName; _fileSize = fileSize; } return self; } @end
(3)测试并结果显示
-(void)archive { //0:归档对象 FileModel *fileModel = [[FileModel alloc] initWithFileType:@".mp3" fileName:@"单身情歌" fileSize:1024.f]; //1:准备路径 NSString *path = [NSHomeDirectory() stringByAppendingString:@"fileModel.plist"]; //2:归档对象 [NSKeyedArchiver archiveRootObject:fileModel toFile:path]; }
-(void)unArchive { //1:解档路径 NSString *path = [NSHomeDirectory() stringByAppendingString:@"fileModel.plist"]; //2:反归档对象 FileModel *fileModel = [NSKeyedUnarchiver unarchiveObjectWithFile:path]; //3:打印属性 NSString *fileName = ((id(*)(id,SEL))objc_msgSend)(fileModel, @selector(fileName)); CGFloat fileSize = ((CGFloat(*)(id,SEL))objc_msgSend)(fileModel, @selector(fileSize)); NSLog(@"fileName = %@",fileName); NSLog(@"fileSize = %lf",fileSize); }
2019-11-11 17:17:59.494655+0800 运行时[3217:245287] fileName = 单身情歌 2019-11-11 17:17:59.494847+0800 运行时[3217:245287] fileSize = 1024.000000
程序猿神奇的手,每时每刻,这双手都在改变着世界的交互方式!