FDFullScreenPopGesture学习笔记
这段时间,公司项目的app需要做全局手势返回,之前也有用过FDFullScreenPopGesture这个开源库,只不过原来要求比较低,就只是单纯的将源码放进去,没有过多研究。但是,目前手上的项目,由于全局使用同一个导航栏,而且,不同地方的导航栏底色不同,导致返回的时候吹出现很多问题。因此,花了一段时间来研究手势返回。
整个手势返回的实现,主要是通过runtime来做的。
Method Swizzling原理(http://blog.csdn.net/yiyaaixuexi/article/details/9374411
http://blog.sina.com.cn/s/blog_916e0cff0101ghxu.html)
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的method实现。
+load vs. +initialize
Swizzling应该在+load方法中实现。
每个类的这两个方法会被Objective-C运行时系统自动调用,+load是在一个类最开始加载时调用,+initialize是在应用中第一次调用该类或它的实例的方式之前调用。这两个方法都是可选的,只有实现了才会被执行。
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(fd_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//为该类添加一个方法
BOOL success = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if(success) {
//添加成功,直接替换
class_replaceMethod(class, originalSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
//未添加成功,交换两个imp
method_exchangeImplementations(originalMethod, swizzledMethod);
}});}
选择器,方法及实现
1.选择器(typedef struct objc_selector *SEL):选择器用于表示一个方法在运行时的名字,一个方法的选择器是一个注册到(或映射到)Objective-C运行时中的C字符串,它是由编译器生成并在类加载的时候被运行时系统自动映射。
2.方法(typedef struct objc_method *Method):一个代表类定义中一个方法的不明类型。
3.实现(typedef id (*IMP)(id, SEL, ...)):这种数据类型是实现某个方法的函数开始位置的 指针,函数使用的是基于当前CPU架构的标准C调用规约。第一个参数是指向self的指针(也就是该类的某个实例的内存空间,或者对于类方法来说,是指向原类的指针)。第二个参数是方法的选择器,后面跟的都是参数。
理解的最好方式:一个类(Class)维护一张调度表(dispatch table)用于解析运行时发送的消息;调度表中的每个实体(entity)都是一个方法(Method),其中key值是一个唯一的名字-选择器(SEL),它对应到一个实现(IMP)--实际上就是指向标准C函数的指针。
在category中无法添加实例属性,因此添加属性的时候通过运行时添加
在@interface UIViewController (FDFullscreenPopGesturePrivate)中的 fd_willAppearInjectBlock属性
//get方法
- (_FDViewControllerWillAppearInjectBlock)fd_willAppearInjectBlock {
return objc_getAssociatedObject(self, _cmd);
}
//set方法
-(void)setFd_willAppearInjectBlock:(_FDViewControllerWillAppearInjectBlock)block {
objc_setAssociatedObject(self, @selector(fd_willAppearInjectBlock), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
UIViewController (FDFullScreenPopGesture)中只是简单地添加了几个属性,这几个属性的作用,都是在UINavigationController (FDFullScreenPopGesture)等相关文件中起作用。
UINavigationController (FDFullscreenPopGesture)
- (void)fd_pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
//判断是否已经添加fd_xxxGesture,否的话,添加该手势 interactivePopGestureRecognizer是iOS7之后添加的左滑返回手势,此处,从该手势上的view的gestures上获得需要使用的属性
if(![self.interactivePopGestureRecognizer.view.gestureRecognizers containsObject:self.fd_fullscreenPopGestureRecognizer]) {
[self.interactivePopGestureRecognizer.view addGestureRecognizer:self.fd_fullscreenPopGestureRecognizer];
//获取左滑手势的所有targets
NSArray *internalTargets = [self.interactivePopGestureRecognizer valueForKey:@"targets'];
//获取相关target
id internalTarget = [internalTargets.firstObject valueForKey:@"target"];
//获取handleNavigationTransition:的sel
SEL internalAction = NSSelectorFromString(@"handleNavigationTransition:");
self.fd_fullscreenPopGestureRecognizer.delegate = self.fd_popGestureRecognizerDelegate;
//为该手势添加target和action
[self.fd_fullscreenPopGestureRecognizer addTarget:internalTarget action:internalAction];
self.interactivePopGestureRecognizer.enabled = NO;
}
[self fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:viewController];
if(![self.viewControllers containsObject:viewController]) {
[self fd_pushViewController:viewController animated:animated];
}
}
- (void)fd_setupViewControllerBasedNavigationBarAppearanceIfNeeded:(UIViewController *)appearingViewController {
if(!self.fd_viewControllerBasedNavigationBarAppearanceEnabled){
return;
}
__weak typeof(self) weakSelf = self;
_FDViewControllerWillAppearInjectBlock block = ^(UIViewController *viewController, BOOL animated) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if(strongSelf) {
//此处的fd_prefersNavigationBarHidden属性就是另一个category中的
[strongSelf setNavigationBarHidden:viewController.fd_prefersNavigationBarHidden animated:animated];
}};
appearingViewController.fd_willAppearInjectBlock = block;
UIViewController *disappearingViewController = self.viewControllers.lastObject;
if(disappearingViewController && !disappearingViewController.fd_willAppearInjectBlock) {
disappearingViewController.fd_willAppearInjectBlock = block;
}}}