Runtime 简单应用:AXKit手势分类实现原理

手势的应用场景很多,如果你觉得系统给我们提供的方法使用起来并不那么方便,那么本文可能对你有帮助,因为我用block对其进行了封装。

应用场景

  • 场景1:为了调试某个功能,快速给一个视图添加手势,要求轻触的时候执行某段代码。
  • 场景2:tabbar按钮双击刷新列表,要求双击的速度在一秒内,执行某段代码。
  • 场景3:给某个图片添加捏合手势以及旋转手势。
  • ……

使用AXKit,可以轻易实现上述几种场景中的需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[self.view ax_addTapGestureHandler:^(UITapGestureRecognizer * _Nonnull sender) {
// 轻触的时候要执行的代码
}];

[self.view ax_addDoubleTapGesture:nil duration:1 handler:^(UITapGestureRecognizer * _Nonnull sender) {
// 双击的时候要执行的代码
}];

// 捏合手势
[self.view ax_addPinchGesture:^(UIPinchGestureRecognizer * _Nonnull sender) {
// 对手势对象(sender)的配置
sender.view.transform = CGAffineTransformScale(sender.view.transform, sender.scale, sender.scale);
sender.scale = 1;
} handler:^(UIPinchGestureRecognizer * _Nonnull sender) {
// 捏合的时候要执行的代码
}];
// 旋转手势
[self.view ax_addRotationGesture:^(UIRotationGestureRecognizer * _Nonnull sender) {
// 对手势对象(sender)的配置
sender.view.transform = CGAffineTransformRotate(sender.view.transform, sender.rotation);
sender.rotation = 0;
} handler:^(UIRotationGestureRecognizer * _Nonnull sender) {
// 旋转的时候要执行的代码
}];

开始使用

推荐CocoaPods方式,在podfile中添加一行:

1
pod 'AXKit'

然后在终端中执行pod install即可完成安装。

AXKit的全局头文件是:

1
2
3
4
// 通过CocoaPods或静态库方式安装
#import <AXKit/AXKit.h>
// 通过手动方式
#import "AXKit.h"

接口声明

直接上头文件源码,注释很详细:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface UIView (AXGestureExtension)

/**
处理tap手势

@param handler 处理手势的block
*/
- (void)ax_addTapGestureHandler:(void (^)(UITapGestureRecognizer *sender))handler;

/**
添加一个tap手势,并处理

@param tap tap
@param handler 处理手势的block
*/
- (void)ax_addTapGesture:(nullable void (^)(UITapGestureRecognizer *sender))tap handler:(void (^)(UITapGestureRecognizer *sender))handler;

/**
添加一个tap手势,并处理,附加动画效果

@param tap tap
@param handler 处理手势的block
@param scale 动画比例
@param duration 持续时间
*/
- (void)ax_addTapGesture:(nullable void (^)(UITapGestureRecognizer *sender))tap handler:(void (^)(UITapGestureRecognizer *sender))handler animatedScale:(CGFloat)scale duration:(NSTimeInterval)duration;

/**
添加一个双击手势,并处理

@param doubleTap double tap
@param duration 双击间隔时间
@param handler 处理手势的block
*/
- (void)ax_addDoubleTapGesture:(nullable void (^)(UITapGestureRecognizer *sender))doubleTap duration:(NSTimeInterval)duration handler:(void (^)(UITapGestureRecognizer *sender))handler;

/**
添加一个长按手势,并处理

@param longPress 长按手势
@param handler 处理手势的block
*/
- (void)ax_addLongPressGesture:(nullable void (^)(UILongPressGestureRecognizer *sender))longPress handler:(void (^)(UILongPressGestureRecognizer *sender))handler;

/**
添加一个轻扫手势,并处理

@param swipe 轻扫
@param handler 处理手势的block
*/
- (void)ax_addSwipeGesture:(nullable void (^)(UISwipeGestureRecognizer *sender))swipe handler:(void (^)(UISwipeGestureRecognizer *sender))handler;

/**
添加一个滑动手势,并处理

@param pan 滑动
@param handler 处理手势的block
*/
- (void)ax_addPanGesture:(nullable void (^)(UIPanGestureRecognizer *sender))pan handler:(void (^)(UIPanGestureRecognizer *sender))handler;

/**
添加一个屏幕边缘滑动手势,并处理

@param screenEdgePan 屏幕边缘滑动
@param handler 处理手势的block
*/
- (void)ax_addScreenEdgePanGesture:(nullable void (^)(UIScreenEdgePanGestureRecognizer *sender))screenEdgePan handler:(void (^)(UIScreenEdgePanGestureRecognizer *sender))handler;

/**
添加一个双指缩放手势,并处理

@param pinch 双指缩放手势
@param handler 处理手势的block

sender.view.transform = CGAffineTransformScale(sender.view.transform, sender.scale, sender.scale);
sender.scale = 1;
*/
- (void)ax_addPinchGesture:(nullable void (^)(UIPinchGestureRecognizer *sender))pinch handler:(void (^)(UIPinchGestureRecognizer *sender))handler;

/**
添加一个双指旋转手势,并处理

@param rotation 双指旋转手势
@param handler 处理手势的block

sender.view.transform = CGAffineTransformRotate(sender.view.transform, sender.rotation);
sender.rotation = 0;
*/
- (void)ax_addRotationGesture:(nullable void (^)(UIRotationGestureRecognizer *sender))rotation handler:(void (^)(UIRotationGestureRecognizer *sender))handler;


@end

NS_ASSUME_NONNULL_END

实现方法

以最简单的tap手势为例,其实现如下:

1
2
3
4
5
6
- (void)ax_addTapGestureHandler:(void (^)(UITapGestureRecognizer *sender))handler{
// 创建手势实例
UITapGestureRecognizer *gesture = [UITapGestureRecognizer new];
// 将手势和target绑定
AXBindGestureAndTarget(gesture, AXDefaultTarget);
}

其中AXBindGestureAndTarget(gesture, AXDefaultTarget)用了inline函数:

1
2
3
static inline void AXBindGestureAndTarget(UIGestureRecognizer *gesture, AXEventTarget *target){
[gesture addTarget:target action:@selector(handleEvent:)];
}

AXDefaultTarget则是宏定义,因为大部分地方用到的都是固定的三个参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#define AXDefaultTarget AXTargetWith(self, gesture, handler)
// 依据self类、手势实例、handler代码块创建一个唯一的target
static inline AXEventTarget *AXTargetWith(UIView *obj, __kindof UIGestureRecognizer *gesture, id handler){
// create a target with <handler>
AXEventTarget *target = [AXEventTarget targetWithHandler:handler];
// add a <gesture> to target
[obj addGestureRecognizer:gesture];
// save target (gesture + handler) to dictionary
NSMutableDictionary *gestures = objc_getAssociatedObject(obj, UIViewGestureAXBlockWrapperKey);
if (!gestures) {
gestures = [NSMutableDictionary dictionary];
objc_setAssociatedObject(obj, UIViewGestureAXBlockWrapperKey, gestures, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
NSMutableSet *handlers = gestures[NSStringFromPointer(gesture)];
if (!handlers) {
handlers = [NSMutableSet set];
gestures[NSStringFromPointer(gesture)] = handlers;
}
[handlers addObject:target];
return target;
}

这里用到了一个AXEventTarget类,这个类的功能就是保存handler,并在需要的时候执行handler。这个分类的实现参考了BlocksKit的实现原理,对其进行扩展,用到了runtime机制,至于什么是runtime,我们稍后再作讨论。本文稍后会进行更新,补充实现的思路、更详细的细节。

附:关于runtime

runtime是运行时一些机制,其中最主要的是消息机制。它与编译时语言的最大区别在于它在运行的时候才去确定要调用的函数类型,如果一个方法没有实现体,那么在编译阶段调用并不会报错,而编译时语言调用一个未实现的函数就会报错。运行时语言调用方法的本质是让对象发送消息,属于动态调用,编译时并不能真正决定调用哪个方法。下面是runtime的一些应用场景:

  • 动态添加方法
  • 拦截系统自带的方法调用(Swizzle)、交换方法
  • json转模型(KVC)
  • 给分类增加属性
  • 实现NSCoding的归档解档
  • 万能控制器跳转
  • JSpatch热更新(苹果不再允许使用热更新)
  • 插件的开发(Xcode8已禁止插件)