這段時間,公司項目的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;
}}}