Objective-C 方法交換實踐(二) - 方法指針交換

来源:http://www.cnblogs.com/v2m_/archive/2017/11/21/7869025.html
-Advertisement-
Play Games

一. 基本函數 1. 根據 sel 得到 class 的實例方法 2. 根據 sel 得到 class 的函數指針 3. 給 class 添加方法 4. 替換 class 的 sel 對應的函數指針,返回值為 sel 對應的原函數指針 5. 交換兩個 method 6. 直接替換 method 的函 ...


一. 基本函數

  1. 根據 sel 得到 class 的實例方法

    Method class_getInstanceMethod(Class cls, SEL name)
  2. 根據 sel 得到 class 的函數指針

    IMP class_getMethodImplementation(Class cls, SEL name)
  3. 給 class 添加方法

    class_addMethod(Class cls, SEL name, IMP imp, const char * types) 
  4. 替換 class 的 sel 對應的函數指針,返回值為 sel 對應的原函數指針

    class_replaceMethod(Class cls, SEL name, IMP imp, const char * types) 
  5. 交換兩個 method

    method_exchangeImplementations(Method m1, Method m2)
  6. 直接替換 method 的函數指針

    method_setImplementation(Method method, IMP imp)

二. 主要問題

1. 原子性操作問題

解決方案一般是在 `+(void)load`方法中處理;也可以加鎖;還可以在`+(void)initialize`中去做,但是一定要註意繼承的問題。

2. 改變範圍超出預期

比如你可能會只想修改一個實例的方法,但實際上你修改了所有的實例方法。比如你交換的方法真實的實現是在父類中的,你的修改會影響所有的父類派生出來的類。
例如,直接使用 `method_exchangeImplementations` 方法,考慮下這種情況
    @ B
    - (void)case1
    {
        NSLog(@"case 1 B");
    }
    @end
    
    @interface C: B
    
    @property (nonatomic, copy) NSString *x;
    
    @end
    
    @implementation C
    - (void)case2
    {
        NSLog(@"case2 C %@-%@",[self class],self.x);
    }
    
    @end
    
    - (void)someMethod {
        Method a1 = class_getInstanceMethod([C class], @selector(case1));
        Method a2 = class_getInstanceMethod([C class], @selector(case2));
        method_exchangeImplementations(a1, a2);
        
        B *b = [[B alloc] init];
        [b case1];
    }

會發生什麼呢?會 crash ,因為 C 作為 B 的子類並沒有實現 case1 方法,方法交換會把 B 的case1 替換成 C 的 case2,後面 [b case1]  其實會執行 void _.._case2(C * self, SEL _cmd) 這個函數,裡面調用 x 屬性,所以 crash。

為了避免這個錯誤,一般的做法有,先用 class_addMethod 判斷能否添加將要替換的方法,如果可以添加,說明子類原先沒有實現此方法,這個方法是在父類中實現的。具體可以看參考1。

RSSwizzlejrswizzle 都避免了這個問題。

3. 可能有命名衝突

比如你交換的方法很可能在別的地方(比如類別里)已經有同樣命名的存在了。此時的避免方法可以是直接去替換 Method 里的函數指針,保存原有的函數指針來調用:
        typedef IMP *IMPPointer;
        
        BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
            IMP imp = NULL;
            Method method = class_getInstanceMethod(class, original);
            if (method) {
                const char *type = method_getTypeEncoding(method);
                imp = class_replaceMethod(class, original, replacement, type);
                if (!imp) {
                    imp = method_getImplementation(method);
                }
            }
            if (imp && store) { *store = imp; }
            return (imp != NULL);
        }
        
        @implementation NSObject (FRRuntimeAdditions)
        + (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
            return class_swizzleMethodAndStore(self, original, replacement, store);
        }
        @end

4. 可能會使用不一樣的方法參數

比如同樣調用原來的函數時,`_cmd`已經不一樣了,解決方案可以和上面一致。

5. 類簇類的swizzling

對於 Objective-C 中的一些類簇類,比如 NSNumber、NSArray和NSMutableArray 等,因為這些並不是一個具體的類,而是一個抽象類,如果直接在這些類的內部寫個方法通過self class等方式來獲取 Class 並做方法交換的話,因為並不能獲得其真實的類名,所以會達不到想要的效果。

比如,我們可以通過以下代碼來得到NSMutableArray的真實類型:

    object_getClass([[NSMutableArray alloc] init]);
    objc_getClass("__NSArrayM");

上面代碼中__NSArrayMNSMutableArray的真實類名;

6. 子類方法調用了 super 方法,並且都做了交換

比如下麵的例子就會發生迴圈調用。
    @ A
    - (void)log {
        NSLog(@"i am a");
    }
    
    - (void)print {
        [self print];
    }
    @end
    
    @ B
    - (void)log {
        NSLog(@"i am b");
        [super log];
    }
    
    - (void)print {
        [self print];
    }
    @end

下麵做一下方法交換,並執行子類的方法。

    - (void)test {
        Method a1 = class_getInstanceMethod([A class], @selector(log));
        Method a2 = class_getInstanceMethod([A class], @selector(print));
        method_exchangeImplementations(a1, a2);
    
        Method a3 = class_getInstanceMethod([B class], @selector(log));
        Method a4 = class_getInstanceMethod([B class], @selector(print));
        method_exchangeImplementations(a3, a4);
        
        B *b = [[B alloc] init];
        [b print];
    }

方法的調用流程(用imp來表示)

    B.log - A.print - B.log....

從而形成了迴圈的引用。

三. 方法交換的實現

1. 直接修改 Method 的函數指針

參考2中提到的,利用 (一、1)中的方法,額外提供一個變數來存儲原始的函數指針,如果需要調用原始方法,就用這個變數來主動設置 sel 參數來防止原始函數用到了_cmd 的情況

2. jrswizzle

主要用到了 method_exchangeImplementations 方法,魯棒性上做的工作就是先做了 class_addMethod 操作。簡單是很簡單,然而上面所說的大部分問題他都不能避免。

3. RSSwizzle

主要用到了 class_replaceMethod 方法,避免了子類的替換影響了父類。而且對方法交換加了鎖,增強了線程安全。有更多的替換選項。並且,他通過block引入了兩個方法互相調用或者子類父類同時交換導致的迴圈問題。上面的問題幾乎都可以避免。
問題是:OSSpinLock 不被建議使用了。
官方文檔說他解決了method_exchangeImplementations 的限制:

  1. 只有在 +load 方法中才線程安全
  2. 對沒有重載的方法交換會遇到非期望的結果
  3. 交換的方法不能依賴 _cmd 參數 (通過 RSSwizzleInfo結構,保存原始的 selector)
  4. 命名衝突

參考:
1.http://nshipster.cn/method-swizzling/
2.https://blog.newrelic.com/2014/04/16/right-way-to-swizzle/
3.http://yulingtianxia.com/blog/2017/04/17/Objective-C-Method-Swizzling/
4.https://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目如下: 網上一般是模擬方塊下落的過程,這種方法簡潔,易於理解,代碼如下: 我剛開始的想法是,為什麼不從最後一行開始,那樣不是更快嗎?後來我發現這個想法不對,從下往上找,要一直遍歷到第0行,這樣的計算量特別大。 下麵,我說說自己的想法:先通過遍歷找到對應方塊在大矩陣中的最下麵一行,然後以這個行數為 ...
  • Python做深度學習之Caffe設計實戰 隨筆背景:在很多時候,很多入門不久的朋友都會問我:我是從其他語言轉到程式開發的,有沒有一些基礎性的資料給我們學習學習呢,你的框架感覺一下太大了,希望有個循序漸進的教程或者視頻來學習就好了。對於學習有困難不知道如何提升自己可以加扣:1225462853進行交 ...
  • Django是一個開源的Web應用框架,由Python寫成。採用MVC的軟體設計模式,主要目標是使得開發複雜的、資料庫驅動的網站變得簡單。Django註重組件的重用性和“可插拔性”,敏捷開發和DRY法則(Don’t Repeat Yoursef)。 花了兩周時間,利用工作間隙時間,開發了一個基於Dj ...
  • 麥子深度學習第三階段深入與強化 隨筆背景:在很多時候,很多入門不久的朋友都會問我:我是從其他語言轉到程式開發的,有沒有一些基礎性的資料給我們學習學習呢,你的框架感覺一下太大了,希望有個循序漸進的教程或者視頻來學習就好了。對於學習有困難不知道如何提升自己可以加扣:1225462853進行交流得到幫助, ...
  • (aspect oriented programming面向切麵編程) 首先在原有的jar包: 需Spring壓縮包中的四個核心JAR包 beans 、context、core 和expression 下載地址: https://pan.baidu.com/s/1qXLHzAW 以及日誌jar包 c ...
  • 關鍵字:演算法工程的類圖,架構分析,設計模式,組合模式 首先,上一個我剛完成的針對上一篇 "Knowledge_SPA——精研查找演算法" 文中使用的工程,所畫的類圖,由此來分析它的架構。如下圖所示: 我們這個工程中使用到了很多設計模式,考慮到了不少設計原則,這一篇又回到了設計模式的學習路線,那麼可以勉 ...
  • IT 行業發展迅速,各種新名詞此起彼伏。身處這樣一個熱點行業,學習是必須的。不打算成為終身學習者的程式員,失業就在明天。 可是,怎麼學呢? 都已經畢業了,每天要上班,不能像以前讀書的時候,整天只是學習,學什麼都有老師教,坐在那兒聽就可以了。 自己從頭看書太辛苦了,網上的文章又太碎片化——是不是報一個 ...
  • IJKPlayer拖動播放進度會導致重新請求數據,未使用已經緩衝好的數據,所以應該儘量控制緩衝區大小,減少不必要的數據損失。 mMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 100 * 102 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...