iOS開發之Runtime常用示例總結

Teri3992 7年前發布 | 8K 次閱讀 iOS開發 移動開發

經常有小伙伴私下在Q上問一些關于 Runtime 的東西,問我有沒有Runtime的相關文章,之前還真沒正兒八經的總結過。之前只是在解析第三方框架源碼時,聊過一些用法,也就是這些第三方框架中用到的Runtime。比如屬性關聯,動態獲取屬性等等。本篇博客就針對Runtime這個主題來總結一些其常用的一些方法,當然“空談誤國”,今天文章中所聊的Runtime依然要依托于本篇博客所涉及的Demo。

本篇文章所聊的Runtime的內容大概有: 動態獲取類名 、 動態獲取類的成員變量 、 動態獲取類的屬性列表 、 動態獲取類的方法列表 、 動態獲取類所遵循的協議列表 、 動態添加新的方法 、 類的實例方法實現的交換 、 動態屬性關聯 、 消息發送與消息轉發機制 等。當然,本篇文章總結的是運行時常用的功能,并不是所有Runtime的內容。

一、構建Runtime測試用例

本篇博客的內容是依托于實例的,所以我們在本篇博客中先構建我們的測試類, Runtime 將會對該類進行相關的操作。下方就是本篇博客所涉及 Demo 的目錄,上面的 RuntimeKit 類是講 Runtime 常用的功能進行了簡單的封裝,而下方的 TestClass 以及相關的類目就是我們 Runtime 要操作的對象了。下方會對 TestClass 以及類目中的內容進行詳細介紹。

下方這幾個截圖就是我們的測試類 TestClass 的主要部分,因為 TestClass 是專門用來測試的類,所以其涉及的內容要盡量的全面。 TestClass 遵循了 NSCoding , NSCopying 這兩個協議,并且為其添加了 公有屬性 、 私有屬性 、 私有成員變量 、 公有實例方法、私有實例方法、類方法等。這些添加的內容,都將是我們Runtime的操作對象。下方那幾個TestClass的類目稍后在使用Runtime時再進行介紹。

二、RuntimeKit的封裝

接下來我們就來看看 RuntimeKit 中的內容,其中對 Runtime 常用的方法進行了簡單的封裝。主要是動態的獲取類的一些屬性和方法的,以及動態方法添加和方法交換的。本部分的干貨還是不少的。

1、獲取類名

動態的獲取類名是比較簡單的,使用 class_getName(Class) 就可以在運行時來獲取類的名稱。 class_getName() 函數返回的是一個 char類型的指針 ,也就是C語言的字符串類型,所以我們要將其轉換成NSString類型,然后再返回出去。下方的 +fetchClassName:方法 就是我們封裝的獲取類名的方法,如下所示:

2、獲取成員變量

下方這個 +fetchIvarList: 這個方法就是我們封裝的獲取類的成員變量的方法。當然我們在獲取成員變量時,可以用 ivar_getTypeEncoding() 來獲取相應成員變量的類型。使用 ivar_getName() 來獲取相應成員變量的名稱。下方就是對獲取成員變量的功能的封裝。返回的是一個數組,數組的元素是一個字典,而字典中存儲的就是相應成員變量的名稱和類型。

下方就是調用上述方法獲取的 TestClass 類的成員變量。當然在運行時就沒有什么私有和公有之分了,只要是成員變量就可以獲取到。在OC中的給類添加成員屬性其實就是添加了一個成員變量和 getter 以及 setter 方法。所以獲取的成員列表中肯定帶有成員屬性,不過成員屬性的名稱前方添加了下劃線來與成員屬性進行區分。我們也可以獲取成員變量的類型,下方的 _var1 是 NSInteger 類型,動態獲取到的是q字母,其實是 NSInteger 的符號。 而i就表示int類型 , c表示Bool類型 , d表示double類型 , f則就表示float類型 。當然這些基本類型都是由一個字母代替的,如果是引用類型的話,則直接就是一個字符串了,比如 NSArray類型就是"@NSArray" 。

3.獲取成員屬性

上面獲取的是類的成員變量,那么下方這個 +fetchPropertyList: 獲取的就是成員屬性。當然此刻獲取的只包括成員屬性,也就是那些有setter或者getter方法的成員變量。下方主要是使用了 class_copyPropertyList(Class,&count) 來獲取的屬性列表,然后通過for循環通過 property_getName() 來獲取每個屬性的名字。當然使用 property_getName() 獲取到的名字依然是C語言的char類型的指針,所以我們還需要將其轉換成NSString類型,然后放到數組中一并返回。如下所示:

下方這個截圖就是調用上述方法獲取的TestClass的所有的屬性,當然 dynamicAddProperty 是我們使用 Runtime 動態給 TestClass 添加的,所以也是可以獲取到的。當然我們獲取到的屬性的名稱為了與其對應的成員變量進行區分,成員屬性的名字前邊是沒有下劃線的。

4、獲取類的實例方法

接下來我們就來封裝一下獲取類的實例方法列表的功能,下方這個 +fetchMethodList: 就是我們封裝的獲取類的實例方法列表的函數。在下方函數中,通過 class_copyMethodList() 方法獲取類的實例方法列表,然后通過for循環使用 method_getName() 來獲取每個方法的名稱,然后將方法的名稱轉換成NSString類型,存儲到數組中一并返回。具體代碼如下所示:

下方這個截圖就是上述方法在TestClass上運行的結果,其中打印了 TestClass 類的所有實例方法,當然其中也必須得包含成員屬性的getter和setter方法。當然TestClass類目中的方法也是必須能獲取到的。結果如下所示:

5、獲取協議列表

下方是獲取我們類所遵循協議列表的方法,主要使用了 class_copyProtocolList() 來獲取列表,然后通過for循序使用 protocol_getName() 來獲取協議的名稱,最后將其轉換成NSString類型放入數組中返回即可。

下方就是我們獲取到的TestClass類所遵循的協議列表:

6、動態添加方法實現

下方就是動態的往相應類上添加方法以及實現。下方的 +addMethod方法 有三個參數,第一個參數是要添加方法的類,第二個參數是方法的SEL,第三個參數則是提供方法實現的 SEL 。稍后在消息發送和消息轉發時會用到下方的方法。下方主要是使用 class_getInstanceMethod() 和 method_getImplementation() 這兩個方法相結合獲取相應 SEL 的方法實現。下方的 IMP 其實就是 Implementation 的方法縮寫,獲取到相應的方法實現后,然后再調用 class_addMethod() 方法將 IMP與SEL進行綁定即可 。具體做法如下所示。

7、方法實現交換

下方就是講類的兩個方法的實現進行交換。如果將 MethodA 與 MethodB 的方法實現進行交換的話,調用 MethodA 時就會執行 MethodB 的內容,反之亦然。

下方這段代碼就是對上述方法的測試。下方是TestClass的一個類目,在該類目中將類目中的方法與TestClass中的方法進行了替換。也就是將method1與method2進行了替換,替換后在method2中調用的method2其實就是調用的method1。在第三方庫中,經常會使用該特性,已達到AOP編程的目的。

三、屬性關聯

屬性關聯說白了就是在類目中動態的為我們的類添加相應的屬性,如果看過之前發布的對 Masonry 框架源碼解析的博客的話,對下方的屬性關聯并不陌生。在 Masonry 框架中就利用 Runtime 的屬性關聯在 UIView 的類目中給 UIView 添加了一個約束數組,用來記錄添加在當前 View 上的所有約束。下方就是在 TestClass 的類目中通過 objc_getAssociatedObject() 和 objc_setAssociatedObject() 兩個方法為 TestClass 類添加了一個 dynamicAddProperty 屬性。上面我們獲取到的屬性列表中就含有該動態添加的成員屬性。

下方就是屬性關聯的具體代碼,如下所示。

四、消息處理與消息轉發

在Runtime中不得不提的就是OC的消息處理和消息轉發機制。當然網上也有不少相關資料,本篇博客為了完整性,還是要聊一下消息處理與消息轉發的。 當你調用一個類的方法時,先在本類中的方法緩存列表中進行查詢,如果在緩存列表中找到了該方法的實現,就執行,如果找不到就在本類中的方列表中進行查找。在本類方列表中查找到相應的方法實現后就進行調用,如果沒找到,就去父類中進行查找。如果在父類中的方法列表中找到了相應方法的實現,那么就執行,否則就執行下方的幾步 。

當調用一個方法在緩存列表,本類中的方法列表以及父類的方法列表找不到相應的實現時,到程序崩潰階段中間還會有幾步讓你來挽救。接下來就來看看這幾步該怎么走。

1.消息處理(Resolve Method)

當在相應的類以及父類中找不到類方法實現時會執行 +resolveInstanceMethod: 這個類方法。該方法如果在類中不被重寫的話,默認返回NO。如果返回NO就表明不做任何處理,走下一步。如果返回 YES 的話,就說明在該方法中對這個找不到實現的方法進行了處理。在該方法中,我們可以為找不到實現的 SEL 動態的添加一個方法實現,添加完畢后,就會執行我們添加的方法實現。這樣,當一個類調用不存在的方法時,就不會崩潰了。具體做法如下所示:

2、消息快速轉發

如果不對上述消息進行處理的話,也就是 +resolveInstanceMethod:返回NO 時,會走下一步消息轉發,即 -forwardingTargetForSelector: 。該方法會返回一個類的對象,這個類的對象有SEL對應的實現,當調用這個找不到的方法時,就會被轉發到SecondClass中去進行處理。這也就是所謂的消息轉發。當該方法返回self或者nil, 說明不對相應的方法進行轉發,那么就該走下一步了。

3.消息常規轉發

如果不將消息轉發給其他類的對象,那么就只能自己進行處理了。如果上述方法返回self的話,會執行 -methodSignatureForSelector: 方法來獲取方法的參數以及返回數據類型,也就是說該方法獲取的是方法的簽名并返回。如果上述方法返回nil的話,那么消息轉發就結束,程序崩潰,報出找不到相應的方法實現的崩潰信息。

在 +resolveInstanceMethod: 返回NO時就會執行下方的方法,下方也是講該方法轉發給 SecondClass ,如下所示:

今天的博客就先到這兒吧,當然還有其他一些 Runtime 的東西本篇博客并未涉及,如果以后解析那個開源庫的源碼時遇到了,我們在單獨聊。依照慣例,本篇博客依附的Demo, 仍然會在 Github 上進行分享,下方是分享鏈接。

 

來自:http://www.cnblogs.com/ludashi/p/6294112.html

 

 本文由用戶 Teri3992 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!