runtime之全屏滑动移除控制器

最近有朋友想了解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>)>>
  • 然后我们要进入头文件看一下有没有提供这个属性


    里面只有2个属性,有一个edges,似乎是看到了曙光
  • 接着我们进去看一下这个属性提供的有哪些值


    提供了上下左右和all

其实细心的朋友就发现了,并没有我们要的值,我们要的是全屏,什么叫全屏,有的朋友说上下左右不是全屏吗? 其实不是,因为还有中间呢?
这个值默认是左侧,也就是我们经常用的从左侧边缘滑动移除控制器,如果不信,你可以打开你的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.打印输出的成员属性


    这个类下面的成员属性很多,其中我们猜测target在 _targets 里面
  • 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的真实类型,所以这个时候军哥要放大招了,打断点来确认真实属性


    真实类型是_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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,653评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,321评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,833评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,472评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,306评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,274评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,658评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,335评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,638评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,697评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,454评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,311评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,699评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,986评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,254评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,647评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,847评论 2 335

推荐阅读更多精彩内容