最近有朋友想了解runtime在这里军哥就浅析一下runtime
- 1.首先你要了解什么是runtime
答: runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API.
在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者
举例: OC : [[YJPerson alloc] init]
runtime : objc_msgSend(objc_msgSend("YJPerson" , "alloc"), "init")
好了关于runtime的更深了解,你可以参考官方文档,或者网上搜索 - 2.接下来进入今天的主题今天讲解一下runtime配合KVC来修改系统的内部的一些东西,那能修改什么东西呢
- 比如说让系统滑动移除控制器
- 修改系统的tabBar
- 修改系统的pageController 的显示图片等等
- 3.这次真的要进入今天的主题了
- 4.今天的目的是
实现全屏滑动
,要实现全屏滑动有2种方法- 4.1 第一种,修改系统的手势,如果系统有提供全屏滑动的方法,就直接修改
- 4.2 第二种,如果系统没有提供,自己创造条件也要实现
系统的滑动手势其实是这个,自定义NavController
UIGestureRecognizer *ges = self.interactivePopGestureRecognizer;
- 4.1 先采用第一种,看看系统里面有没有提供全屏滑动的手势
打印这个属性得知,interactivePopGestureRecognizer
的真实类型是UIScreenEdgePanGestureRecognizer
具体请看
<UIScreenEdgePanGestureRecognizer: 0x7f860f70d460; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7f860f401fa0>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7f860f70cfe0>)>>
-
然后我们要进入头文件看一下有没有提供这个属性
-
接着我们进去看一下这个属性提供的有哪些值
其实细心的朋友就发现了,并没有我们要的值,我们要的是全屏,什么叫全屏,有的朋友说上下左右不是全屏吗? 其实不是,因为还有中间呢?
这个值默认是左侧,也就是我们经常用的从左侧边缘滑动移除控制器,如果不信,你可以打开你的iPhone的设置界面,随便push一个控制器,然后从左侧边缘按住屏幕往右滑,这个就是左侧滑动移除控制器,有的朋友说你为什么不让打开应用呢,因为现在有的应用把这个功能取消了,这个功能是在iOS7之后才有的
第一种方案pass掉了
- 4.2 没有条件我们创造条件也要实现,这是程序员的一种正常思维
系统不是不提供吗,我们也不能修改,所以我们要创造条件,怎么创造条件呢,其实滑动说白了就是手势,那我们就添加一个手势
代码如下:
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGes:)];
[self.view addGestureRecognizer:pan];
- 其实写到这里我们又遇到了一个问题,什么问题呢,就是调用当前对象的
panGes :
方法,那么这个条件是什么呢,有的朋友说当屏幕滑动到小于一般让他复位,当屏幕滑动大于一半,让他移除,等等,其实你考虑的不够全面,因为他还夹杂着速度等等一些条件,我们在之前将UI进阶的时候讲过用2个view,或者3个view来实现,记得当时用了3节课才讲完,这个如果要手动实现,困难度要比2个view或者3个view难多了,用一天估计也讲不完. - 那最终方案是什么呢?其实这个时候我们要看一下我们缺什么?其实自己添加手势无非是缺Target和action,也就是调用哪个对象的哪个方法, action,我们想一下系统既然能实现,那说明系统内部是有这个方法的,那我们是不是直接拿过来用就可以了,想到这里我们再回去看一下在一开始打印的信息
<UIScreenEdgePanGestureRecognizer: 0x7f860f70d460; state = Possible; delaysTouchesBegan = YES; view = <UILayoutContainerView 0x7f860f401fa0>; target= <(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7f860f70cfe0>)>>
你有没有发现action=handleNavigationTransition:
到此我们的action是有了,那现在唯一缺的就是Target
- 那么Target怎么获取,这个时候我们就想到用KVC取出来通过
id target = [ges valueForKeyPath:@""];
但是用KVC的前提条件是必须知道这个属性的真实类型,才能取出,很显然现在我们是不知道的,所以接下来我们的终极目的就是得到target的真实属性,那么怎么才能得到呢,接下来军哥就要放大招了,那就是runtime
- 首先你要用runtime必须先导入这个函数库
#import <objc/runtime.h>
有的朋友不知道什么是函数库,其实你稍微有一点点OC编程经验的就知道,函数库其实就是封装了好多方法供你调用,其实他也没有什么牛xx之处
- 5.要获取某一个类下面的所有成员属性,可以用这个方法,这里说明一下这个方法class_copyIvarList,只能获取某一个类下面的成员属性,也就是不能获取他的子类,或者父类的成员属性,所以我们必须要获取他爷爷类下面的成员属性,因为根据推测,target在UIPanGestureRecognizer这个类下面,因为
initWithTarget:action这个方法在UIPanGestureRecognizer这个类下面
/**
获取某一个类下面的所有成员属性
@param cls#> 获取的那个类
@param outCount#> 这个类下面的成员属性的个数
@return 返回这个类下面的成员属性数组
*/
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([UIPanGestureRecognizer class], &outCount);
- 6.接下来就开始遍历,因为有数组,有索引,所以我们就开始遍历
/**
获取某一个类下面的所有成员属性
cls 获取的那个类
outCount这个类下面的成员属性的个数
返回这个类下面的成员属性数组
*/
unsigned int outCount = 0;
// 说明 Ivar是C语言的成员变量,这个方法是拷贝一份这个类下面的成员变量
Ivar *ivars = class_copyIvarList([UIGestureRecognizer class], &outCount);
for (int i = 0; i< outCount; i++) {
//ivar_getName 获取某一个成员变量下面的名称,因为这个方法返回的类型是const char *所以需要把char转换为NSString,直接包装成对象就可以了
NSString *name = @(ivar_getName(ivars[i]));
NSLog(@"%@",name);
}
-
7.打印输出的成员属性
- 8.接下来我们开始验证一下
// 打印,输出了很多成员属性,其中最重要的成员属性是_targets,据此我们猜测,target可能存在于_targets里面,然后我们尝试一下
id target = [ges valueForKeyPath:@"_targets"];
NSLog(@"-- %@",target);
打印输出
2016-12-18 22:30:00.698 runtime之全屏滑动移除控制器[12311:313247] -- (
"(action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7fd16dc01bf0>)"
)
如果做过后台的朋友就知道,看到()代表是数组,所以他返回的仍然是数组,其实也很好理解,为什么呢,因为假如你添加了很多手势,所有的target都在这个下面
- 9.把数组去掉
NSArray *targetArray = [ges valueForKeyPath:@"_targets"];
// 去掉数组
id target = targetArray[0];
NSLog(@"-- %@",target);
去掉数组打印如下
2016-12-18 22:33:13.766 runtime之全屏滑动移除控制器[12362:315288] -- (action=handleNavigationTransition:, target=<_UINavigationInteractiveTransition 0x7ff7c9d073e0>)
-
10.去掉数组你会发现,还是不能确定target的真实类型,所以这个时候军哥要放大招了,打断点来确认真实属性
- 11.到此全屏滑动搞定,完整代码如下
UIGestureRecognizer *ges = self.interactivePopGestureRecognizer;
// 打印,输出了很多成员属性,其中最重要的成员属性是_targets,据此我们猜测,target可能存在于_targets里面,然后我们尝试一下
NSArray *targetArray = [ges valueForKeyPath:@"_targets"];
// 去掉数组
id tempTarget = targetArray[0];
// 获取target
id target = [tempTarget valueForKeyPath:@"_target"];
// 消除方法弃用(过时)的警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// 添加全屏滑动手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];
#pragma clang diagnostic pop
[self.view addGestureRecognizer:pan];
ps: #pragma clang diagnostic push是为了消除警告
具体可以参照我的另一篇文章iOS消除警告
- 12.因为官方文档明确说明了跟控制器不能出栈,所以当你滑动到跟控制器的是需要禁止手势,如果你不禁止就会出现卡死的现象,bug很严重
具体代码如下:
UIGestureRecognizer *ges = self.interactivePopGestureRecognizer;
// 打印,输出了很多成员属性,其中最重要的成员属性是_targets,据此我们猜测,target可能存在于_targets里面,然后我们尝试一下
NSArray *targetArray = [ges valueForKeyPath:@"_targets"];
// 去掉数组
id tempTarget = targetArray[0];
// 获取target
id target = [tempTarget valueForKeyPath:@"_target"];
// 消除方法弃用(过时)的警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// 添加全屏滑动手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:@selector(handleNavigationTransition:)];
#pragma clang diagnostic pop
[self.view addGestureRecognizer:pan];
pan.delegate = self;
}
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
if (self.viewControllers.count > 1) {
// 非根控制器
return YES;
}else{
// 跟控制器
return NO;
}
}
到此大功告成,小伙伴们,可以去试一下
如果你对runtime还有点晕的话,我自己录制的视频可以参考一下
链接: https://pan.baidu.com/s/1miJgfBM 密码: ah5u
示例代码
持续更新实用的干货
微信公众号iOS精汇
简书coderYJ,微博coderYJ