前一段时间使用Runtime 觉得非常有意思,一直没有时间整理.
现在被问了一些Runtime的东西,所以在这里记录下.
这里用一个场景来说明Runtime的属性列表的使用:归档解档.
正常情况下你要完成一个对象(数据model)的归档解档.你要在数据model的类继承NSCoding 协议.
// Copyright © 2017年 like学. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface PersonModel : NSObject<NSCoding>
@property (nonatomic, copy) NSString * name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString * name1;
@property (nonatomic, copy) NSString * name2;
@property (nonatomic, copy) NSString * name3;
@end
----------------------------------------
// Copyright © 2017年 like学. All rights reserved.
//
#import "PersonModel.h"
@interface PersonModel()
@property (nonatomic, copy) NSString * age1;
@end
@implementation PersonModel
// 告诉归档那些属性
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeInteger:_age forKey:@"age"];
}
// 解档属性
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
@end
外面对这个进行操作
- (IBAction)setButtonValue:(id)sender {
NSLog(@"存数据 归档");
PersonModel * p = [[PersonModel alloc]init];
p.name = @"like";
p.age = 12;
// 存进去
NSString * temp = NSTemporaryDirectory();
// 拼接路径 文件可以随便命名
NSString * filePath = [temp stringByAppendingString:@"like.like"];
// 归档的是一个对象 p
[NSKeyedArchiver archiveRootObject:p toFile:filePath];
}
- (IBAction)getButtonValue:(id)sender {
NSLog(@"取数据 解档");
NSString * temp = NSTemporaryDirectory();
// 拼接路径
NSString * filePath = [temp stringByAppendingString:@"like.like"];
PersonModel * p = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@---%ld",p.name,(long)p.age);
}
正常情况下的数据序列化是这样的.你的数据model类中每加一个属性你都要在归档解档的方法中添加对应的代码,感觉非常的繁琐.也不想这样麻烦.
想着,把数据类的属性名字和属性个数都拿到进行for循环操作不就好了.
思路是通的,问题是怎么实现.
ok.Runtime 来拯救
#import "ViewController.h"
#import "PersonModel.h"
#import <objc/runtime.h>
@interface ViewController ()
@end
@implementation ViewController
/**
oc的序列化和反序列化(归档解档)
runtime
*/
- (void)viewDidLoad {
[super viewDidLoad];
unsigned int count = 0;
// c语言函数参数 特点 如果是基本数据类型的指针! 基本上在函数内部会改变它的值
// class_copyIvarList(<#__unsafe_unretained Class cls#>, <#unsigned int *outCount#>)
// 第一参数 要对应的类
// 第二参数 要指针参数
// 这个方法拿到对应类的属性个数 包含.m属性的个数
// 返回值是指针 指向的是首地址 向下取值可以拿到下一个值,类似数组但是不会像数组那样越界爆炸,可以继续取值,但是是别人的东西了
// Ivar 结构体
// 在C语言 函数有copy new create 在堆(堆内存程序员管理,栈是系统管理的)内存开辟空间且是连续的
Ivar * ivars = class_copyIvarList([PersonModel class], &count);
NSLog(@"+++==%d",count);
Ivar ivar = ivars[0];
const char * name = ivar_getName(ivar);
NSLog(@"%s--",name);
}
了解基本的原理之后就可以这样使用
原来的model数据类 的归解档
// Copyright © 2017年 like学. All rights reserved.
//
#import "PersonModel.h"
#import <objc/runtime.h>
@interface PersonModel()
@property (nonatomic, copy) NSString * age1;
@end
@implementation PersonModel
// 告诉归档那些属性
-(void)encodeWithCoder:(NSCoder *)aCoder {
// [aCoder encodeObject:_name forKey:@"name"];
// [aCoder encodeInteger:_age forKey:@"age"];
//
//
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([self class], &count);
for (int i =0; i< count; i++) {
//拿到Ivar
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
NSString * key = [NSString stringWithUTF8String:name];
// 归档 kvc 不要管是否 基本数据类型 取也是这个样式
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
// 这个大的花括号是个栈区 要把它给手动释放掉
free(ivars);
}
// 解档属性
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
// _name = [aDecoder decodeObjectForKey:@"name"];
// _age = [aDecoder decodeIntegerForKey:@"age"];
unsigned int count = 0;
Ivar * ivars = class_copyIvarList([self class], &count);
for (int i =0; i< count; i++) {
//拿到Ivar
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
NSString * key = [NSString stringWithUTF8String:name];
// 解档
id value = [aDecoder decodeObjectForKey:key];
// kvc 赋值
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end
这样就可以使用了,无论你在model的数据类中加多少属性,也不用在一个一个的添加了.
并且可以写成一个工具类,放在项目中.