Runtime简介
Runtime
是一套底层的C语言API(包含了很多强大实用的C语言数据类型和C语言函数), 实际上,平时我们编写的OC代码,底层都是基于Runtime
实现的,也就是说,平时我们编写的OC代码,最终都是转成了底层的Runtime
代码。
对于C语言,函数的调用会在编译阶段决定调用哪个函数
对于OC的函数,属于动态调用过程的,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
相关函数
对对象进行操作的方法一般以`object_`开头
对类进行操作的方法一般以`class_`开头
对类或对象的方法进行操作的方法一般以`method_`开头
对成员变量进行操作的方法一般以`ivar_`开头
对属性进行操作的方法一般以`property_`开头开头
对协议进行操作的方法一般以`protocol_`开头
根据以上的函数的前缀 可以大致了解到层级关系。对于以objc_
开头的方法,则是runtime`最终的管家,可以获取内存中类的加载信息,类的列表,关联对象和关联属性等操作。
Runtime应用
动态的获取模型属性和模型属性类型
之前封装了一个简单的自动化类库,根据模型的变动来决定表结构。如果模型中多了一个属性,那么对应的数据表中就要多一个对应的字段,同理,如果模型中有一个属性我不要了,那么表结构对应的字段也要删掉。这里就是运用了运行时,动态的去获取模型的属性和属性类型,通过比对,如果有变化则进行更新操作。
下面,贴一段简单的代码:
//根据运行时动态的获取模型属性名称和类型;
+(NSMutableArray * )getModelNameType:(Class)cls
{
unsigned int outCount;
Ivar * ivarList=class_copyIvarList(cls, &outCount);
NSMutableArray * muArray=[NSMutableArray array];
//获取用户不想创建的字段;
NSArray * ignoreArray;
if ([cls instancesRespondToSelector:@selector(ignorePropertyNames)]) {
ignoreArray=[[cls new] ignorePropertyNames];
}
for (int i=0; i
实现Pop全屏手势
添加全屏Pop手势我能够想到的有两种思路:
- 自己在push出来的View中添加
UIPanGestureRecognizer
手势。监听手势滑动,随着手势滑动,逐渐退出控制器的View,但是这种方式实现起来感觉较为麻烦。 - 利用运行时机制,获取系统的Pop手势的
target
和action
,创建自己的手势,同时给自己创建的手势添加监听事件的时候使用上面获得的target
和action
即可。下面就以第二种方式来实现:
因为系统UIPanGestureRecognizer
手势中没有暴露出这个target
和action
,所以我们可以通过利用运行时机制来获取遍历:
首先我们自定义一个继承自 UINavigationController
的类,在该类中进行操作:
- (void)viewDidLoad {
[super viewDidLoad];
//获取系统滑动手势
UIGestureRecognizer * systemGes =self.interactivePopGestureRecognizer;
//获取系统手势所添加的View
UIView * vc=systemGes.view;
unsigned int count= 0;
//class_copyIvarList:把成员属性列表复制一份出来
Ivar * ivarList= class_copyIvarList([UIGestureRecognizer class], &count);
for (int i=0; i
打印结果:
_targets====@"NSMutableArray"
_delayedTouches====@"NSMutableArray"
_delayedPresses====@"NSMutableArray"
_view====@"UIView"
_lastTouchTimestamp====d
_state====q
_allowedTouchTypes====q
_initialTouchType====q
_internalActiveTouches====@"NSMutableSet"
_forceClassifier====@"_UIForceLevelClassifier"
_requiredPreviewForceState====q
_touchForceObservable====@"_UITouchForceObservable"
"NSObservation"
_forceTargets====@"NSMutableArray"
_forcePressCount====Q
_beganObservable====@"NSObservationSource"
_failureRequirements====@"NSMutableSet"
_failureDependents====@"NSMutableSet"
_delegate====@""
_allowedPressTypes====@"NSArray"
_gestureEnvironment====@"UIGestureEnvironment"
好了,重点来了,我们可以看到第一个属性名是_targets
,类型是NSMutableArray
,我们来打印下看看这个_targets
是个什么鬼
NSMutableArray * array=[systemGes valueForKeyPath:@"_targets"];
NSLog(@"%@",array);
打印结果:
(
"(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7ff9f3500da0>)"
)
可以看到这个数组里面只有这一项,并且终于看到了我们想要的action
和target
,这就好办了,只要我们拿到这两个鬼,然后添加到我们自己创建的手势上就大功告成了。
- (void)viewDidLoad {
[super viewDidLoad];
//获取系统滑动手势
UIGestureRecognizer * systemGes =self.interactivePopGestureRecognizer;
//获取系统手势所添加的View
UIView * vc=systemGes.view;
unsigned int count= 0;
//class_copyIvarList:把成员属性列表复制一份出来
Ivar * ivarList= class_copyIvarList([UIGestureRecognizer class], &count);
NSMutableArray * array=[systemGes valueForKeyPath:@"_targets"];
//取出_targets对象。
id targetObjct= array[0];
//取出target
id target = [targetObjct valueForKeyPath:@"target"];
//获取action
SEL action = NSSelectorFromString(@"handleNavigationTransition:");
//创建属于我们自己的手势
UIPanGestureRecognizer * ges=[[UIPanGestureRecognizer alloc]initWithTarget:target action:action];
[vc addGestureRecognizer:ges];
}
字典转模型
字典转模型KVC实现
KVC:遍历字典中所有key,去模型中查找有没有对应的属性。这个是要求字典中的Key,必须要在模型里能找到相应的值,如果找不到就会报错。基本原理如下:
// KVC原理:遍历字典中所有key,去模型中查找有没有对应的属性
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull value, BOOL * _Nonnull stop) {
// 去模型中查找有没有对应属性 KVC
// key:source value:baidu
// [item setValue:@"baidu" forKey:@"source"]
[item setValue:value forKey:key];
}];
但是,在实际开发中,从字典中取值,不一定要全部取出来。因此,我们可以通过重写KVC
中的forUndefinedKey
这个方法,就不会进行报错处理。
// 解决KVC报错
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
//如果字典中的key是 id 我们就将 id对应的值赋值给 ID ,相当于做了一个转换
if ([key isEqualToString:@"id"]) {
_ID = [value integerValue];
}
// key:没有找到key value:没有找到key对应的值
NSLog(@"%@ %@",key,value);
}
字典转模型Runtime实现
KVC是通过遍历字典中所有key,去模型中查找有没有对应的属性。而Runtime的思路是通过遍历模型的值,从字典中取值。是不是与KVC反过来了。
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
id objc = [[self alloc] init];
// count:成员变量个数
unsigned int count = 0;
// 获取成员变量数组
Ivar *ivarList = class_copyIvarList(self, &count);
// 遍历所有成员变量
for (int i = 0; i < count; i++) {
// 获取成员变量
Ivar ivar = ivarList[i];
// 获取成员变量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 获取成员变量类型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
// 获取key
NSString *key = [ivarName substringFromIndex:1];
// 去字典中查找对应value
// key:user value:NSDictionary
id value = dict[key];
// 二级转换:判断下value是否是字典,如果是,字典转换层对应的模型
// 并且是自定义对象才需要转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
// 字典转换成模型 userDict => User模型
// 转换成哪个模型
// 获取类
Class modelClass = NSClassFromString(ivarType);
value = [modelClass modelWithDict:value];
}
// 给模型中属性赋值
if (value) {
[objc setValue:value forKey:key];
}
}
return objc;
}
交换方法
交换方法实现的需求场景:自己创建了一个功能性的方法,在项目中多次被引用,当项目的需求发生改变时,要使用另一种功能来代替这个功能,但是最好是不改变旧的项目。
可以在类的分类中,再写一个新的方法(是符合新的需求的),然后交换两个方法的实现。这样,在不改变项目的代码,而只是增加了新的代码 的情况下,就完成了项目的改进。
交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次,而initialize方法会在类或者子类在 第一次使用的时候调用,当有分类的时候会调用多次。
比如:给imageNamed方法提供功能,每次加载图片就判断下图片是否加载成功。
@implementation UIImage (Image)
// 加载分类到内存的时候调用
+ (void)load
{
// 获取imageWithName方法地址
Method imageWithName = class_getClassMethod(self, @selector(imageWithName:));
// 获取imageNamed方法地址
Method imageName = class_getClassMethod(self, @selector(imageNamed:));
// 交换方法地址,相当于交换实现方式
method_exchangeImplementations(imageWithName, imageName);
}
// 既能加载图片又能打印错误信息
+ (instancetype)imageWithName:(NSString *)name
{
// 这里调用imageWithName,相当于调用imageName
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"加载空的图片");
}
return image;
}
@end
补充
字典转模型的第一步是要先有一个模型对象,而模型属性通常需要跟字典中的key一一对应
问题:一个一个的生成模型属性,很慢?
需求:能不能自动根据一个字典,生成对应的属性。
解决:提供一个分类,专门根据字典生成对应的属性字符串
#import
@interface NSObject (Property)
+ (void)createPropertyCodeWithDict:(NSDictionary *)dict;
@end
#import "NSObject+Property.h"
@implementation NSObject (Property)
+ (void)createPropertyCodeWithDict:(NSDictionary *)dict
{
NSMutableString *strM = [NSMutableString string];
// 遍历字典
[dict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull propertyName, id _Nonnull value, BOOL * _Nonnull stop) {
// NSLog(@"%@ %@",propertyName,[value class]);
NSString *code;
if ([value isKindOfClass:NSClassFromString(@"__NSCFString")]) {
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) int %@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFArray")]){
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSArray *%@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSDictionary *%@;",propertyName]
;
}else if ([value isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
code = [NSString stringWithFormat:@"@property (nonatomic, assign) BOOL %@;",propertyName]
;
}
[strM appendFormat:@"\n%@\n",code];
}];
NSLog(@"%@",strM);
}
@end
这样NSLog打印出来的就是后台返回给你的模型属性,不用手动在一个个的敲了,是不是省去很多麻烦?