都说Runtime是Objective-C中的黑魔法,这里有关于OC Runtime的详细介绍。而我们在用Swift的时候,很多时候也需要用到Runtime,它与在OC中的使用还是有一点点区别的。在解决UIButton重复点击问题时主要用到了Runtime中的关联对象(Associated Objects)和方法交叉(Method Swizzling)。
因为在Swift代码中已经没有了Objective-C的运行时消息机制, 在代码编译时即确定了其实际调用的方法. 所以纯粹的Swift类和对象没有办法使用runtime, 更不存在method swizzling. 那是不是在swift中就不能使用runtime了呢?答案肯定是可以的。要使用runtime,我们需要在想要使用runtime的方法或者属性前面加上**dynamic关键字。**
Associated Objects
在解决UIButton重复点击问题时,首先我们需要两个变量,一个是点击的间隔时间,另一个是是否需要忽视点击事件的Bool值,那么我们就需要将这两个变量与UIButton关联起来。
1 | extension UIButton { |
Method Swizzling
接下来我们需要写一个用来替换UIButton点击事件的方法,在这个方法里来处理重复点击的问题。UIButton继承自UIControl,在传递点击事件是会触发这个方法:
1 | public func sendAction(action: Selector, to target: AnyObject?, forEvent event: UIEvent?) |
于是我们重写用来交叉的方法:
1 | // SwizzledMethod |
接下来,就是用我们自己重写的SwizzeldMethod来替换UIButton的OriginalMethod:
1 | // swift现不支持重写+load方法 |
这个方法,就是在运行时将UIButton的sendAction(:to:forEvent:)替换为我们自己写的my_sendAction(:to:forEvent:)。
load vs. initialize
在 Swift 中 load 类方法永远不会被 runtime 调用,因此在+load方法中实现交叉就变成了不可能的事。出于安全性和一致性的考虑,我们必须确保相关的方法交叉在一个 dispatchonce 中,这样也是很安全的。还有一种方法,在 app delegate 中实现方法交叉,
不像上面通过类扩展进行方法交叉,而是简单地在 app delegate 的 application(:didFinishLaunchingWithOptions:) 方法调用时中执行相关代码也是可以的。基于对类的修改,这种方法应该就足够确保这些代码会被执行到。