iOS中的runTime
运行时,我们最常见的可能就是创建类别(Category),为类别添加属性,类别是不能直接添加属性的,可以添加方法,但是加上运行时,便可以添加属性。还有一种就是使用运行时,通过获取属性列表,成员变量列表,方法列表,协议列表,MJExtension就是利用了运行时进行字典与模型转换。现在我们来实现这两种做法。
为类别添加属性
创建继承Object的Category并引入
#import <objc/runtime.h>
定义一个属性
/**
标记属性
*/
@property (nonatomic , copy)NSString * rumTimeString;
固定一个对应的key
const char * key1 = "rumTimeString";
初始化getter和setter
然后实现objc_getAssociatedObject和objc_setAssociatedObject方法
const char * key1 = "rumTimeString";
@implementation NSObject (Object)
@dynamic rumTimeString;
- (NSString *)rumTimeString
{
return objc_getAssociatedObject(self, key1);
}
- (void)setRumTimeString:(NSString *)rumTimeString
{
objc_setAssociatedObject(self, key1, rumTimeString, OBJC_ASSOCIATION_COPY);
}
到此就结束了就完成属性的添加了
获取属性列表字典转模型
创建一个类方法
+ (id)initWithDict:(NSDictionary *)dict;
//初始化对象
id obj = [[self class] new];
if (obj) {
}
//获取类的属性及属性对应的类型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
//获取属性列表与个数
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
//遍历属性
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通过property_getName函数获得属性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通过property_getAttributes函数可以获得属性的名字和@encode编码
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//释放properties指向的内存
free(properties);
//给属性赋值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[obj setValue:[dict valueForKey:key] forKey:key];
}
最后返回
return obj;
全部代码
+ (id)initWithDict:(NSDictionary *)dict {
id obj = [[self class] new];
if (obj) {
//获取类的属性及属性对应的类型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//通过property_getName函数获得属性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
//通过property_getAttributes函数可以获得属性的名字和@encode编码
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
//释放properties指向的内存
free(properties);
//给属性赋值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[obj setValue:[dict valueForKey:key] forKey:key];
}
}
return obj;
}
创建Model,并定义属性,引用上面的方法得到model
@interface Model : NSObject
@property (nonatomic , assign)NSUInteger age;
@property (nonatomic , copy)NSString * testString;
@end
NSDictionary * dict = @{@"age":@1,@"testString":@"属性列表赋值"};
Model * model = [Model initWithDict:dict];
NSLog(@"%@",model.testString);
如果模型存在嵌套模型,单纯这样处理还是不够。需要再加判断与循环遍历。
比如这里如果出现model中再嵌套一个model01
@interface Model01 : NSObject
@property (nonatomic , copy)NSString * testStr;
@end
@interface Model : NSObject
@property (nonatomic , assign)NSUInteger age;
@property (nonatomic , copy)NSString * testString;
@property (nonatomic , strong)NSArray * testArr;
@property (nonatomic , strong)Model01 * m01;
@end
按照刚才的处理
NSDictionary * dict = @{@"age":@1,@"testString":@"属性列表修改",@"testArr":@[@"1",@"2"],@"m01":@{@"testStr":@"model01赋值"}};
Model * model = [Model initWithDict:dict];
[model getPropertyList];
NSLog(@"%@",model.m01.testStr);
这里肯定是得不到testStr的。
那该怎么处理,可以添加一个递归循环处理。
//给属性赋值
int i = 0;
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
if ( [[dict valueForKey:key] isKindOfClass:[NSDictionary class]]) {
NSDictionary * dict2 = [dict valueForKey:key];
NSLog(@"第二层%@",key.superclass);
id classobj = [attributes[i] componentsSeparatedByString:@"\""][1] ;//获取属性所属class名字
id obj02 = [NSClassFromString(classobj) initWithDict:dict2];//递归循环,如果碰到value是字典,那么就遍历,并给嵌套的model初始化赋值。
[obj setValue:obj02 forKey:key];//将已经嵌套的model赋值后再赋值给上一层model。Model01对象(赋值OK)-->这一层循环结后交给Model对象的obj的m01。
}else
{
[obj setValue:[dict valueForKey:key] forKey:key];
}
i ++;
}
}
return obj;
重新跑,会发现,model.m01.testStr,拿到了model01赋值。
Method Swizzling方法交换
好像之前看到有人说,交换灵魂,什么魔术师之类的,我承认土鳖。
UIViewController里面有个方法是viewWillAppear,现在把这个方法替换掉。
创建一个类别#import "UIViewController+Swizzling.h"
写一个类方法+ (void)load
,引入#import <objc/runtime.h>
写一个新方法,代替viewWillAppear,注意的是这里的+ (void)load
是#import <objc/runtime.h>方法中的一个。并不是无中生有。
- (void)new_viewWillAppear:(BOOL)animated {
NSLog(@"只要加载viewWillAppear: %@", self);
}
使用运行时实现+ (void)load
如下
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = self;
SEL originalSelector = @selector(viewWillAppear:);//原方法选择器
SEL swizzledSelector = @selector(new_viewWillAppear:);//替换后方法选择器
Method originalMethod = class_getInstanceMethod(class, originalSelector);//原方法
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);//替换方法
//添加
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
//替换
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);//双互交换
}
});
}
然后在ViewController引入#import "UIViewController+Swizzling.h"
就完事了,然后你每次跑起来的时候,都会运行到new_viewWillAppear
。
现在重新创建一个方法- (void)newload
并实现如下
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(riginalMethod_click);
SEL swizzledSelector = @selector(swizzledMethod_click);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);//双互交换
}
});
}
注意,如果你这里的方法是写在#import "UIViewController+Swizzling.h"
那么引用的时候要确定你当前类有
riginalMethod_click
swizzledMethod_click
这两个方法,别无脑崩。实现方法如下
image.png
引用newload与与方法riginalMethod_click
[self newload];
[self riginalMethod_click];
跑起来,然后会发现,打印的并不是“原方法”而是“替换方法”。