簡(jiǎn)介
method swizzling指方法替換.
每一個(gè)類(lèi)內(nèi)部都存在一個(gè)類(lèi)似字典的列表. 方法名( SEL)為Key, 與其對(duì)應(yīng)的Value則是指向該方法實(shí)現(xiàn)的指針(IMP). method swizzling可以在runtime時(shí)將SEL和IMP進(jìn)行更換. 比如SELa1原來(lái)對(duì)應(yīng)IMPa1, SELa2原來(lái)對(duì)應(yīng)IMPa2. method swizzling后鲫忍,SELa1就可以對(duì)應(yīng) IMPa2, SELa2就對(duì)應(yīng)IMPa1.
警告??
method swizzling一定要保證唯一性和原子性. 唯一性是指應(yīng)該盡可能在 +load 方法中實(shí)現(xiàn), 這樣可以保證方法一定會(huì)調(diào)用且不會(huì)出現(xiàn)異常. 原子性是指使用 dispatch_once 來(lái)執(zhí)行方法交換, 這樣可以保證只運(yùn)行一次. (Swift中的DispatchQueue并沒(méi)有once的方法, 筆者自行添加了DispatchQueue.once方法的實(shí)現(xiàn). )
不要輕易使用 method swizzling. 因?yàn)閯?dòng)態(tài)交換方法實(shí)現(xiàn)并沒(méi)有編譯器的安全保證, 可能會(huì)在運(yùn)行時(shí)造成奇怪的bug.
使用場(chǎng)景: 比如在版本開(kāi)發(fā)末期, 增加了一項(xiàng)需求: 不改變?cè)笮〉那疤嵯聦pp內(nèi)所有UILabel的字體更換為某個(gè)新的字體. 如果最初動(dòng)手開(kāi)發(fā)時(shí)沒(méi)有將字體寫(xiě)成動(dòng)態(tài)獲取, 那使用method swizzling是一個(gè)方便的解決方法.
解決思路: 將系統(tǒng)的setText:方法替換為我們自己實(shí)現(xiàn)的swizzled_setText(text:), 并在新方法內(nèi)調(diào)整字體大小.
附實(shí)現(xiàn)
注: 以下代碼需要寫(xiě)入U(xiǎn)ILabel的擴(kuò)展(extension)里
public static func initializeMethod() {
if self != UILabel.self {
return
}
DispatchQueue.once {
// UILabel的setText(:)方法屬于setter方法, 無(wú)法直接進(jìn)行調(diào)用或者替換. 所以直接寫(xiě)成 #selector(UILabel.setText(:))會(huì)報(bào)錯(cuò).
// 對(duì)于這類(lèi)成員變量setter方法的替換, 我們可以直接用setter方法對(duì)應(yīng)的字符串去初始化我們想要的Selector對(duì)象
let originalSelector = Selector("setText:")
let swizzledSelector = #selector(UILabel.swizzled_setText(text:))
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
// 在進(jìn)行 Swizzling 的時(shí)候,需要用 class_addMethod 先進(jìn)行判斷一下原有類(lèi)中是否有要替換方法的實(shí)現(xiàn)
let didAddMethod: Bool = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
// 如果 class_addMethod 返回 yes,說(shuō)明當(dāng)前類(lèi)中沒(méi)有要替換方法的實(shí)現(xiàn),所以需要在父類(lèi)中查找,這時(shí)候就用到 method_getImplemetation 去獲取 class_getInstanceMethod 里面的方法實(shí)現(xiàn),然后再進(jìn)行 class_replaceMethod 來(lái)實(shí)現(xiàn) Swizzing
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
}
@objc func swizzled_setText(text: String) -> Void {
self.swizzled_setText(text: text)
self.font = UIFont.init(name: EnumSet.FontType.Normal.rawValue, size: self.font.pointSize)
}
注: 以下代碼需要寫(xiě)入DispatchQueue的擴(kuò)展(extension)里
private static var _onceTracker = [String]()
public class func once(file: String = #file,
function: String = #function,
line: Int = #line,
block: () -> Void) {
let token = "\(file):\(function):\(line)"
once(token: token, block: block)
}
public class func once(token: String,
block: () -> Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
guard !_onceTracker.contains(token) else { return }
_onceTracker.append(token)
block()
}