在Swift中使用method swizzling的一點(diǎn)心得

簡(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()
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末浑侥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辖试,更是在濱河造成了極大的恐慌,老刑警劉巖院崇,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迈着,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡怖辆,警方通過(guò)查閱死者的電腦和手機(jī)祷肯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)沉填,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人佑笋,你說(shuō)我怎么就攤上這事“弑牵” “怎么了蒋纬?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)坚弱。 經(jīng)常有香客問(wèn)我蜀备,道長(zhǎng),這世上最難降的妖魔是什么荒叶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任碾阁,我火速辦了婚禮,結(jié)果婚禮上些楣,老公的妹妹穿的比我還像新娘脂凶。我一直安慰自己,他們只是感情好愁茁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布蚕钦。 她就那樣靜靜地躺著,像睡著了一般鹅很。 火紅的嫁衣襯著肌膚如雪嘶居。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天促煮,我揣著相機(jī)與錄音邮屁,去河邊找鬼。 笑死菠齿,一個(gè)胖子當(dāng)著我的面吹牛佑吝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泞当,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼迹蛤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了襟士?” 一聲冷哼從身側(cè)響起盗飒,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陋桂,沒(méi)想到半個(gè)月后逆趣,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嗜历,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年宣渗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抖所。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡痕囱,死狀恐怖田轧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鞍恢,我是刑警寧澤傻粘,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站帮掉,受9級(jí)特大地震影響弦悉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蟆炊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一稽莉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涩搓,春花似錦污秆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至疾层,卻和暖如春将饺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背痛黎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工予弧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人湖饱。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓掖蛤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親井厌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蚓庭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355