在Objective-C的項(xiàng)目中, 經(jīng)常遇到通過(guò)runtime來(lái)獲取類(lèi)和對(duì)象的成員變量, 屬性, 方法, 在此基礎(chǔ)上可以實(shí)現(xiàn)method swizzling.
關(guān)于runtime的相關(guān)內(nèi)容, 請(qǐng)參考博客:
iOS --- 理解Runtime機(jī)制及其使用場(chǎng)景
iOS---防止UIButton重復(fù)點(diǎn)擊的三種實(shí)現(xiàn)方式
iOS --- 使用runtime解決3D Touch導(dǎo)致UIImagePicker崩潰的問(wèn)題
JSPatch即使用JavaScriptCore.framework, 使用JS代碼調(diào)用任何OC的原生接口, 通過(guò)runtime來(lái)替換任意OC的原生方法, 以此來(lái)實(shí)現(xiàn)實(shí)時(shí)地修復(fù)線上bug.
Swift中如何使用runtime
Swift代碼中已經(jīng)沒(méi)有了Objective-C的運(yùn)行時(shí)消息機(jī)制, 在代碼編譯時(shí)即確定了其實(shí)際調(diào)用的方法. 所以純粹的Swift類(lèi)和對(duì)象沒(méi)有辦法使用runtime, 更不存在method swizzling.
為了兼容Objective-C, 凡是繼承NSObject的類(lèi)都會(huì)保留其動(dòng)態(tài)性, 依然遵循Objective-C的?運(yùn)行時(shí)消息機(jī)制, 因此可以通過(guò)runtime獲取其屬性和方法, 實(shí)現(xiàn)method swizzling.
請(qǐng)看如下的代碼:
//
// UIButton+CSExtension.swift
// CSSwiftExtension
//
// Created by Chris Hu on 16/6/20.
// Copyright ? 2016年 icetime17. All rights reserved.
//
import UIKit
// MARK: - UIButton Related
public extension UIButton {
private struct cs_associatedKeys {
static var accpetEventInterval = "cs_acceptEventInterval"
static var acceptEventTime = "cs_acceptEventTime"
}
// 重復(fù)點(diǎn)擊的間隔
var cs_accpetEventInterval: NSTimeInterval {
get {
if let accpetEventInterval = objc_getAssociatedObject(self, &cs_associatedKeys.accpetEventInterval) as? NSTimeInterval {
return accpetEventInterval
}
return 1.0
}
set {
objc_setAssociatedObject(self, &cs_associatedKeys.accpetEventInterval, newValue as NSTimeInterval, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var cs_acceptEventTime: NSTimeInterval {
get {
if let acceptEventTime = objc_getAssociatedObject(self, &cs_associatedKeys.acceptEventTime) as? NSTimeInterval {
return acceptEventTime
}
return 1.0
}
set {
objc_setAssociatedObject(self, &cs_associatedKeys.acceptEventTime, newValue as NSTimeInterval, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
override public class func initialize() {
let before: Method = class_getInstanceMethod(self, #selector(UIButton.sendAction(_:to:forEvent:)))
let after: Method = class_getInstanceMethod(self, #selector(UIButton.cs_sendAction(_:to:forEvent:)))
method_exchangeImplementations(before, after)
}
func cs_sendAction(action: Selector, to target: AnyObject?, forEvent event: UIEvent?) {
if NSDate().timeIntervalSince1970 - self.cs_acceptEventTime < self.cs_accpetEventInterval {
return
}
if self.cs_accpetEventInterval > 0 {
self.cs_acceptEventTime = NSDate().timeIntervalSince1970
}
self.cs_sendAction(action, to: target, forEvent: event)
}
}
以上, 即通過(guò)runtime的方式解決UIButton的重復(fù)點(diǎn)擊問(wèn)題.
UIButton繼承自NSObject, 因此遵循runtime. 事實(shí)上, 對(duì)于基本框架如Foundation, UIKit等, 都可以使用runtime.
這里, 要注意Swift的代碼與Objective-C代碼的語(yǔ)法區(qū)別.
同時(shí), 對(duì)于一般OC代碼的method swizzling, 在load方法中執(zhí)行即可. 而Swift沒(méi)有l(wèi)oad, 所以要在initialize中執(zhí)行.
使用方式:
btn.cs_accpetEventInterval = 1.0
Swift中的@objc和dynamic關(guān)鍵字
繼承自NSObject的類(lèi)都遵循runtime, 那么純粹的Swift類(lèi)呢?
在屬性和方法之前加上@objc關(guān)鍵字, 則一般情況下可以在runtime中使用了. 但有一些情況下, Swift會(huì)做靜態(tài)優(yōu)化而無(wú)法使用runtime.
要想完全使得屬性和方法被動(dòng)態(tài)調(diào)用, 必須使用dynamic關(guān)鍵字. 而dynamic關(guān)鍵字會(huì)隱式地加上@objc來(lái)修飾.
獲取Swift類(lèi)的runtime信息的方法, ?要加上Swift模塊名:
id cls = objc_getClass("DemoSwift.MySwiftClass")
關(guān)于Demo
本文的Demo請(qǐng)參考CSSwiftExtension.
這是是一個(gè)Swift的extension集合, 包含了一些常見(jiàn)的方法.