runTime 是ios底层运行时机制,他可以动态获取类的成员属性,变量,更可以改变这些东西!
要使用runTime ,必须先导入头文件:#import <objc/message.h>
字典转模型实例: 这是h文件,m文件无需写任何代码
@interface Lender : NSObject{
CGFloat height;
}
@property (nonatomic, copy) NSString*name;
@property (nonatomic, strong) NSNumber*age;
@property (nonatomic, assign)intno;@end
@end
下面就用RunTime来做些事情,比如获取这些变量或者改变这些东西
#import<objc/message.h>
unsignedintoutCount = 0;
Ivar*vars = class_copyIvarList([Lender class], &outCount);//获取到所有的变量列表
for(inti =0; i < outCount; i++) {//遍历所有的成员变量
Ivar ivar= vars[i];//取出第i个位置的成员变量
constchar*propertyName = ivar_getName(ivar);//通过变量获取变量名
constchar*propertyType = ivar_getTypeEncoding(ivar);//获取变量编码类型
printf("---%s--%s\n", propertyName, propertyType);
}
打印结果
---height--f
---_name--@"NSString"
---_age--@"NSNumber"
---_no--i
可见,通过这几句简单的代码就可以获取到某个类中所有变量的名称和类型,然后通过object_setIvar()方法为具体某个对象的某个成员变量赋值。
其他常用情景(熟记)
1.动态(dynamic)调换(exchange)两个方法(method)的执行(implementation)。常用于类别中(catagory)。
2.动态遍历类所有的成员变量(property),实现数据字典(Dictionary)快速转实体类(模型)。常用于数据-模型处理。
3.动态遍历类所有的成员变量(property),实现归档(encode)与解档(decode)。
4.动态关联。可以给分类新增成员变量。(分类本是不允许添加成员变量的。)
一、动态调换方法:
NSMutableArray有个自带的方法addObject:(id)object,这个参数object是不能为nil的,如果这样调用[array addObject:nil],程序会无情crash。我要实现就算添加了nil,程序也不会crash。
新建类别(NSMutableArray+KXX),并在NSMutableArray+KXX.m文件中导入头文件。
在NSMutableArray+KXX.m文件中,实现方法- (void)newMethodToAddObject:(id)objdect。
- (void)newMethodToAddObject:(id)object {
if (!object) {
NSLog(@"object为空,已被处理!!!");
}
}
在NSMutableArray+KXX.m文件中,实现方法+ (void)load。这个方法会在对象内存被分配、初始化后就自动调用。
+ (void)load {
// NSMutableArray 真正的类名是 __NSArrayM!!!! NSMutableDictionary同理。
Method _originMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(addObject:));
Method _newMethod = class_getInstanceMethod([self class], @selector(newMethodToAddObject:));
// 调换两个方法的执行
method_exchangeImplementations(_originMethod, _newMethod);
}
在main.m文件测试。
#import "NSMutableArray+KXX.h"
#import <objc/message.h>
@implementation NSMutableArray (KXX)
- (void)newMethodToAddObject:(id)object {
if (!object) {NSLog(@"object为空,已被处理!!!");}}
+ (void)load {
// NSMutableArray 真正的类名是 __NSArrayM。NSMutableDictionary同理。
Method _originMethod = class_getInstanceMethod(objc_getClass("__NSArrayM"), @selector(addObject:));
Method _newMethod = class_getInstanceMethod([self class], @selector(newMethodToAddObject:));
// 调换两个方法的执行
method_exchangeImplementations(_originMethod, _newMethod);
}
@end
运行后虽然有警告,但是不会crash.输出为
2016-03-04 20:02:53.101 runtime[55523:513861] object为空,已被处理!!!
Program ended with exit code: 0
二、字典转实体(模型)
从网络上下载的json数据解析出字典后,可以利用runtime来快速转成实体类。而不用一个一个的给实体属性赋值。还有就是当数据字典的键(key)和实体的成员变量不一致时的处理,这个时候使用字典的映射(map)。
1.新建一个继承于NSObject的类ModelBaseClass,这个作为一会儿实体类的模型基类。
2.在基类构建setter方法。
// 构建setter方法
- (SEL)createSetterMethodWithPropertyName:(NSString *)name {
if (!name) {
return nil;
}
// 首字母大写。
name = [NSString stringWithFormat:@"set%@:", name.capitalizedString];
// 生成setter方法。
return NSSelectorFromString(name);
}
3.构建获取映射字典方法
// 获取字典映射,返回nil是为了子类重写该方法。
- (NSDictionary *)getMapDictionary {
return nil;
}
4.分情况如果是key不一样(需要映射字典)和key一样(其实也可以不用分,就按不一样处理)
// 字典转模型。这是没有字典映射的,数据字典的key必须和model的成员变量一致。
- (void)assignValueToModelWithNoMapDictionary:(NSDictionary *)dict {
if (!dict) {
return;
}
// 取出所有的key
NSArray *keys = [dict allKeys];
for (int i = 0; i < [keys count]; i ++) {
// 生成selector
SEL setSel = [self createSetterMethodWithPropertyName:keys[i]];
if ([self respondsToSelector:setSel]) {
NSString *value = [NSString stringWithFormat:@"%@", dict[keys[i]]];
[self performSelectorOnMainThread:setSel withObject:value waitUntilDone:YES];
}
}
}
// 这是有字典映射的。
- (void)assignValueToModelWithDictionary:(NSDictionary *)dict {
// 取得映射字典。
NSDictionary *mapDict = [self getMapDictionary];
// 转换成key和数据字典一样的字典。
NSArray *dataDictKeys = [dict allKeys];
NSMutableDictionary *tempDict = [NSMutableDictionary dictionary];
for (int i = 0; i < [dataDictKeys count]; i ++) {
NSString *key = dataDictKeys[i];
[tempDict setValue:dict[key] forKey:mapDict[key]];
}
[self assignValueToModelWithNoMapDictionary:tempDict];
}
-
5.新建实体类Model,继承于ModelBaseClass。并导入头文件
6.在Model.h文件创建两个成员变量。
#import "ModelBaseClass.h"
@interface Model : ModelBaseClass
@property (nonatomic, copy)NSString *name;
@property (nonatomic, copy)NSString *gender;
@end
7.完成构造方法。这里要分情况,是否需要字典映射。
- (instancetype)initWithDataDictionary:(NSDictionary *)dict{
self = [super init];
if (self) {
//
if (![self getMapDictionary]) {
[self assignValueToModelWithNoMapDictionary:dict];
}
else {
[self assignValueToModelWithDictionary:dict];
}
}
return self;
}
// 便利构造
+ (instancetype)modelWithDictionary:(NSDictionary *)dict{
return [[self alloc] initWithDataDictionary:dict];
}
8.创建生成getter方法
// 取得getter方法
- (SEL)createGetterMethodWithPropertyName:(NSString *)name{
return NSSelectorFromString(name);
}
9.获取类的所有的成员变量。
// 取得所有的成员变量
- (NSArray *)getAllProperties {
NSMutableArray *array = [NSMutableArray array];
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i ++) {
objc_property_t property = properties[i];
const char *propertyString = property_getName(property);
NSString *string = [NSString stringWithUTF8String:propertyString];
[array addObject:string];
}
free(properties);
return array;
}
10.通过类和方法的签名来获取调用对象。
// 通过方法和类的签名来调用对象。实现取值
- (void)displayProperty {
// 取得所有成员变量
NSArray *properties = [self getAllProperties];
for (int i = 0; i < [properties count]; i ++) {
// 取得getter
SEL getSel = [self createGetterMethodWithPropertyName:properties[i]];
if ([self respondsToSelector:getSel]) {
// 获取类和方法签名
NSMethodSignature *signature = [self methodSignatureForSelector:getSel];
// 从签名获得调用对象
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
// 添加target
[invocation setTarget:self];
// 添加selector
[invocation setSelector:getSel];
// 设置接收返回的值
NSObject *__unsafe_unretained returnValue = nil;
// 调用
[invocation invoke];
// 接收返回值
[invocation setReturnValue:&returnValue];
}
}
}
11.重写父类的取得映射字典的方法。这个字典的键就是数据字典的key,值就是类的成员变量。
- (NSDictionary *)getMapDictionary {
return @{@"姓名":@"name", @"性别":@"gender", @"这个没有":@"(づ。◕‿‿◕。)づ"};
}
12.如果数据字典的成员个数多余类的成员变量数量,就会造成NSMutableDictionary的setObject:(id)object forKey:(NSString *)key方法的object参数为nil,程序就会crash.为了解决这个问题,新建一个NSMutableDictionary的分类NSMutableDictionary+KXX.
#import "NSMutableDictionary+KXX.h"
#import
@implementation NSMutableDictionary (KXX)
- (void)newMethodSetObject:(id)object forKey:(NSString *)key{
if (!key) {
NSLog(@"nil 已被处理!!!");
}
[self newMethodSetObject:object forKey:key];
}
+ (void)load {
Method _origin = class_getInstanceMethod(objc_getClass("__NSDictionaryM"), @selector(setObject:forKey:));
Method _new = class_getInstanceMethod([self class], @selector(newMethodSetObject:forKey:));
method_exchangeImplementations(_origin, _new);
}
@end
13.在main.m测试
#import
#import "Model.h"
#import "NSMutableDictionary+KXX.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSDictionary *mockDict = @{@"姓名":@"邝大爷", @"性别":@"Male", @"这个没有":@"(づ。◕‿‿◕。)づ"};
Model *model = [Model modelWithDictionary:mockDict];
NSLog(@"%@\n------>\n%@\n", model.name, model.gender);
}
return 0;
}
成功!!!输出
2016-03-04 21:43:13.248 Model[63415:591984] Kxx.xxQ -----> Male
Program ended with exit code: 0
三、归档和解档
如果需要实现一些基本数据的数据持久化(data persistance)或者数据共享(data share)。我们可以选择归档和解档。如果用一般的方法:
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:@"nameKey"];
[aCoder encodeObject:self.gender forKey:@"genderKey"];
[aCoder encodeObject:[NSNumber numberWithInteger:self.age] forKey:@"ageKey"];
}
也可以实现。但是如果实体类有很多的成员变量,这种方法很显然就无力了。
这个时候,我们就可以利用runtime来实现快速归档、解档:
1.让实体类遵循协议。并在.m文件导入头文件。
2.实现- (instancetype)initWithCoder:(NSCoder *)aDecoder和- (void)encodeWithCoder:(NSCoder *)aCoder方法。
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
//
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i ++) {
objc_property_t property = properties[i];
const char *propertyChar = property_getName(property);
NSString *propertyString = [NSString stringWithUTF8String:propertyChar];
id value = [aDecoder decodeObjectForKey:propertyString];
[self setValue:value forKey:propertyString];
}
free(properties);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i ++) {
objc_property_t property = properties[i];
const char *propertyChar = property_getName(property);
NSString *propertyString = [NSString stringWithUTF8String:propertyChar];
id object = [self valueForKey:propertyString];
[aCoder encodeObject:object forKey:propertyString];
}
free(properties);
}
3.在main.m文件测试归档、解档。
#import
#import "Model.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Model *model = [Model new];
model.name = @"kxx";
model.age = 24;
model.gender = @"male";
NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"modelData.data"];
// 归档
BOOL flag = [NSKeyedArchiver archiveRootObject:model toFile:path];
if (flag) {
NSLog(@"archive object successfully!!!");
}
// 解档
Model *unarchivedModel = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSLog(@"\n\n%@\n%@\n%ld", unarchivedModel.name, unarchivedModel.gender, unarchivedModel.age);
}
return 0;
}
4.控制台输出
2016-03-06 18:52:32.368 NSCoder[38672:320789] archive object successfully!!!
2016-03-06 18:52:32.369 NSCoder[38672:320789]
kxx
male
24
Program ended with exit code: 0
5.查看路径文件会有一个数据文件存在:
四、动态关联对象
比如Objective-C是不能向一个类的类别(catagory)添加新成员变量的。但是我们可以利用runtime来实现动态添加成员变量。先把关联策略列出来:
Behavior @propertyequivalent Description
OBJC_ASSOCIATION_ASSIGN@property(assgin) or@property(unsafe_unretained)弱引用
OBJC_ASSOCIATION_RETAIN_NONATOMIC@property(nonatomic,strong)强引用,线程不安全的
OBJC_ASSOCIATION_COPY_NONATOMIC@property(nonatomic,copy)可拷贝,线程不安全的
OBJC_ASSOCIATION_RETAIN@property(atomic,strong)强引用,线程安全的
OBJC_ASSOCIATION_COPY@property(atomic,copy)属性是可拷贝的,线程安全的
添加私有变量(private variables)来让方法执行更加完善。
给类别(catagory)增加公共属性来配置它的行为。
给KVO模式创建一个相关联的观察者(observer)。
新建一个NSObject类的类别NSObject+AssociatedObject。
在.h文件新增一个成员变量:
@property (nonatomic, strong)id newProperty;
然后在.m文件的implementation下面加上关键字@dynamic,表示这个新增的成员变量是动态加载的。否则类别是不能使用新增成员变量的。并且导入头文件。
#import "NSObject+AssociatedObject.h"
@implementation NSObject (AssociatedObject)
@dynamic newProperty;
@end
设置全局的静态的一会儿关联对象要用的唯一的key值:
static const void *uniqueKey = "newPropertyKey";
然后就是设置新成员变量的setter方法。
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
参数说明:
id object:需要关联属性的对象。一般是self。
const void *key:这就是刚刚设置的唯一的key。
id value: 关联属性的值。
objc_AssociationgPolicy policy:关联策略。(详见上面表格)
// setter
- (void)setNewProperty:(id)newProperty {
objc_setAssociatedObject(self, uniqueKey, newProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
然后就是设置getter方法。
// getter
- (id)newProperty {
return objc_getAssociatedObject(self, uniqueKey);
}
在main.m文件测试:
#import
#import "NSObject+AssociatedObject.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object = [NSObject new];
[object setNewProperty:@"Hello World"];
NSString *result = object.newProperty;
NSLog(@"%@", result);
}
return 0;
}
ok,输出:
2016-03-07 10:52:41.097 AssociatedObject[6237:58203] Hello World
Program ended with exit code: 0