首先附上项目地址:
FDFullscreenPopGesture
本篇使用1.1版本源码进行学习
此项目以AOP的方式实现了UINavigationController的“一行代码实现全屏滑动返回”功能。其主要功能是通过UINavigationController内置的interactivePopGestureRecognizer手势对象。通过使用自定义的UIPanGestureRecognizer类实例绑定原手势的action及SEL,使响应范围扩大到整个屏幕,巧妙地实现了此功能。详细解释可以查看原博客轻松学习之二——iOS利用Runtime自定义控制器POP手势动画或本人的读后解析UINavigationController的全屏拖动返回 。
1. 主要结构说明
整个项目非常简单,对外只有一组.h及.m文件。代码量也只有区区两三百行,足够轻量且高效,值得我等膜拜学习~
1.1 头文件的结构说明
项目对外公开的类及主要功能为:
- UINavigationController的扩展类:UINavigationController + FDFullscreenPopGesture
名称 | 类型 | 说明 |
---|---|---|
fd_fullscreenPopGestureRecognizer | 属性 | 真正的全屏拖动响应的手势对象 |
fd_viewControllerBasedNavigationBarAppearanceEnabled | 属性 | 是否允许ViewController对象自定义导航栏外观(默认为YES) |
- UIViewController的扩展类:UIViewController + FDFullscreenPopGesture
主要用于ViewController对象自主控制全屏手势的使能(以便ViewController的视图也处理自定义的拖动手势,防止手势冲突)
名称 | 类型 | 说明 |
---|---|---|
fd_interactivePopDisabled | 属性 | 是否允许响应UINavigationController的全屏拖动手势 |
fd_prefersNavigationBarHidden | 属性 | 表名当前ViewController对象的导航栏是否隐藏(默认为NO) |
1.2 实现文件的结构说明
- 遵循UIGestureRecognizerDelegate协议的类:_FDFullscreenPopGestureRecognizerDelegate
名称 | 类型 | 说明 |
---|---|---|
navigationController | weak属性 | 内部需要根据导航器对象做处理 |
内部实现了gestureRecognizerShouldBegin:方法,用于处理不同情况下是否响应拖动手势。
设计此类的主要目的是将此功能独立出来,作为UINavigationController + FDFullscreenPopGesture的工具类使用,简化了原类代码,使结构更加清晰。
- UIViewController的私有扩展类:UIViewController + FDFullscreenPopGesturePrivate
名称 | 类型 | 说明 |
---|---|---|
fd_willAppearInjectBlock | block对象,属性 | 保存注入的block对象到堆内存,等待合适时机执行【按命名来说,在ViewWillAppear方法中执行】 |
在运行时交换了ViewWillAppear:方法实现,在其中注入执行了block对象。
- UINavigationController + FDFullscreenPopGesture的实现
交换了pushViewController:animated:方法实现,在内部实现了主要功能:
- 使用自定义的UIPanGestureRecognizer对象绑定了原有手势的action和SEL,添加到UINavigationController的切换控制视图上。
- 声明并实现了注入的_FDViewControllerWillAppearInjectBlock对象,并赋值给指定的UIViewController对象。
- UIViewController + FDFullscreenPopGesture的实现
只是对扩展中声明的属性进行实现。
2. 使用Category扩展原类,并向指定方法注入新的功能
在OC中,一般是通过Category对Class进行扩展的方式来实现AOP的。本项目即是如此。例如,通过对UINavigationController进行扩展,在load时对方法进行替换,将手势添加到导航器控制视图上,并绑定上原来的target和SEL:
|
|
如此实现,即可在引入头文件后,对应的UINavigationController实例自动添加全屏返回手势,对原类的耦合性降至最低。
本项目中,UIViewController的扩展也使用了此方式,以达到在ViewWillAppear:方法中执行注入block,实现运行时对导航栏隐藏设置的修改。充分利用了OC语言的动态性,使代码简洁易用,降低耦合。
3. 防止block对self捕获产生的引用循环,并保证block执行时的完整性
在fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:方法中,作者初始化了UIViewController对象在ViewWillAppear:方法中需要注入的block。先看代码:
|
|
其中,最为重要的是:
|
|
我们都知道,使用weak修饰的OC对象,可以防止NSMallocBlock对象在捕获时对其添加强引用(详见《Objective-C高级编程 iOS与OS X多线程与内存管理》16),避免产生引用循环。但是这样有一个弊端,就是假设当self释放后,若block在此时执行,self对象自动置为nil,预定的功能可能就无法正常实现了。为了防止这种情况发生,可以声明一个strong修饰的局部指针变量,指向捕获的弱对象,既可以保证在block执行过程中self不被释放,还可以避免对self进行强引用(strong变量会在作用域外被自动release)。
4. 总结
- FDFullscreenPopGesture主要通过Category的方式对UINavigationController和UIViewController进行功能扩展,避免了Class继承方式的“笨重”,简化了引入和使用。
- 在Category的load方法中,替换了原类的方法实现(原类的load加载要在扩展类之前),在其中注入了指定功能。
- 使用KVC的方式,巧妙地进行UIGestureRecognizer对象的替换,近乎“无损”地实现了全屏滑动返回功能,避免了使用传统的交互式动画方式实现自定义的UINavigationController。
- 使用weak对象避免block的引用循环,并用strong对象保证block执行过程中捕获对象不被释放。