常常有小夥伴私下在Q上問一些關於Runtime的東西,問我有沒有Runtime的相關博客,以前還真沒正兒八經的總結過。以前只是在解析第三方框架源碼時,聊過一些用法,也就是這些第三方框架中用到的Runtime。好比屬性關聯,動態獲取屬性等等。本篇博客就針對Runtime這個主題來總結一些其經常使用的一些方法,固然「空談誤國」,今天博客中所聊的Runtime依然要依託於本篇博客所涉及的Demo。編程
本篇博客所聊的Runtime的內容大概有:動態獲取類名、動態獲取類的成員變量、動態獲取類的屬性列表、動態獲取類的方法列表、動態獲取類所遵循的協議列表、動態添加新的方法、類的實例方法實現的交換、動態屬性關聯、消息發送與消息轉發機制等。固然,本篇博客總結的是運行時經常使用的功能,並非全部Runtime的內容。數組
1、構建Runtime測試用例緩存
本篇博客的內容是依託於實例的,因此咱們在本篇博客中先構建咱們的測試類,Runtime將會對該類進行相關的操做。下方就是本篇博客所涉及Demo的目錄,上面的RuntimeKit類是講Runtime經常使用的功能進行了簡單的封裝,而下方的TestClass以及相關的類目就是咱們Runtime要操做的對象了。下方會對TestClass以及類目中的內容進行詳細介紹。框架
下方這幾個截圖就是咱們的測試類TestClass的主要部分,由於TestClass是專門用來測試的類,因此其涉及的內容要儘可能的全面。TestClass遵循了NSCoding, NSCopying這兩個協議,而且爲其添加了公有屬性、私有屬性、私有成員變量、 公有實例方法、私有實例方法、類方法等。這些添加的內容,都將是咱們Runtime的操做對象。下方那幾個TestClass的類目稍後在使用Runtime時再進行介紹。函數
2、RuntimeKit的封裝測試
接下來咱們就來看看RuntimeKit中的內容,其中對Runtime經常使用的方法進行了簡單的封裝。主要是動態的獲取類的一些屬性和方法的,以及動態方法添加和方法交換的。本部分的乾貨仍是很多的。fetch
一、獲取類名3d
動態的獲取類名是比較簡單的,使用class_getName(Class)就能夠在運行時來獲取類的名稱。class_getName()函數返回的是一個char類型的指針,也就是C語言的字符串類型,因此咱們要將其轉換成NSString類型,而後再返回出去。下方的+fetchClassName:方法就是咱們封裝的獲取類名的方法,以下所示:指針
二、獲取成員變量對象
下方這個+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添加的,因此也是能夠獲取到的。固然咱們獲取到的屬性的名稱爲了與其對應的成員變量進行區分,成員屬性的名字前邊是沒有下劃線的。
四、獲取類的實例方法
接下來咱們就來封裝一下獲取類的實例方法列表的功能,下方這個+fetchMethodList:就是咱們封裝的獲取類的實例方法列表的函數。在下方函數中,經過class_copyMethodList()方法獲取類的實例方法列表,而後經過for循環使用method_getName()來獲取每一個方法的名稱,而後將方法的名稱轉換成NSString類型,存儲到數組中一併返回。具體代碼以下所示:
下方這個截圖就是上述方法在TestClass上運行的結果,其中打印了TestClass類的全部實例方法,固然其中也必須得包含成員屬性的getter和setter方法。固然TestClass類目中的方法也是必須能獲取到的。結果以下所示:
五、獲取協議列表
下方是獲取咱們類所遵循協議列表的方法,主要使用了class_copyProtocolList()來獲取列表,而後經過for循序使用protocol_getName()來獲取協議的名稱,最後將其轉換成NSString類型放入數組中返回便可。
下方就是咱們獲取到的TestClass類所遵循的協議列表:
六、動態添加方法實現
下方就是動態的往相應類上添加方法以及實現。下方的+addMethod方法有三個參數,第一個參數是要添加方法的類,第二個參數是方法的SEL,第三個參數則是提供方法實現的SEL。稍後在消息發送和消息轉發時會用到下方的方法。下方主要是使用class_getInstanceMethod()和method_getImplementation()這兩個方法相結合獲取相應SEL的方法實現。下方的IMP其實就是Implementation的方法縮寫,獲取到相應的方法實現後,而後再調用class_addMethod()方法將IMP與SEL進行綁定便可。具體作法以下所示。
七、方法實現交換
下方就是講類的兩個方法的實現進行交換。若是將MethodA與MethodB的方法實現進行交換的話,調用MethodA時就會執行MethodB的內容,反之亦然。
下方這段代碼就是對上述方法的測試。下方是TestClass的一個類目,在該類目中將類目中的方法與TestClass中的方法進行了替換。也就是將method1與method2進行了替換,替換後在method2中調用的method2其實就是調用的method1。在第三方庫中,常常會使用該特性,已達到AOP編程的目的。
3、屬性關聯
屬性關聯說白了就是在類目中動態的爲咱們的類添加相應的屬性,若是看過以前發佈的對Masonry框架源碼解析的博客的話,對下方的屬性關聯並不陌生。在Masonry框架中就利用Runtime的屬性關聯在UIView的類目中給UIView添加了一個約束數組,用來記錄添加在當前View上的全部約束。下方就是在TestClass的類目中經過objc_getAssociatedObject()和objc_setAssociatedObject()兩個方法爲TestClass類添加了一個dynamicAddProperty屬性。上面咱們獲取到的屬性列表中就含有該動態添加的成員屬性。
下方就是屬性關聯的具體代碼,以下所示。
4、消息處理與消息轉發
在Runtime中不得不提的就是OC的消息處理和消息轉發機制。固然網上也有很多相關資料,本篇博客爲了完整性,仍是要聊一下消息處理與消息轉發的。當你調用一個類的方法時,先在本類中的方法緩存列表中進行查詢,若是在緩存列表中找到了該方法的實現,就執行,若是找不到就在本類中的方列表中進行查找。在本類方列表中查找到相應的方法實現後就進行調用,若是沒找到,就去父類中進行查找。若是在父類中的方法列表中找到了相應方法的實現,那麼就執行,不然就執行下方的幾步。
當調用一個方法在緩存列表,本類中的方法列表以及父類的方法列表找不到相應的實現時,到程序崩潰階段中間還會有幾步讓你來挽救。接下來就來看看這幾步該怎麼走。
1.消息處理(Resolve Method)
當在相應的類以及父類中找不到類方法實現時會執行+resolveInstanceMethod:這個類方法。該方法若是在類中不被重寫的話,默認返回NO。若是返回NO就代表不作任何處理,走下一步。若是返回YES的話,就說明在該方法中對這個找不到實現的方法進行了處理。在該方法中,咱們能夠爲找不到實現的SEL動態的添加一個方法實現,添加完畢後,就會執行咱們添加的方法實現。這樣,當一個類調用不存在的方法時,就不會崩潰了。具體作法以下所示:
二、消息快速轉發
若是不對上述消息進行處理的話,也就是+resolveInstanceMethod:返回NO時,會走下一步消息轉發,即-forwardingTargetForSelector:。該方法會返回一個類的對象,這個類的對象有SEL對應的實現,當調用這個找不到的方法時,就會被轉發到SecondClass中去進行處理。這也就是所謂的消息轉發。當該方法返回self或者nil, 說明不對相應的方法進行轉發,那麼就該走下一步了。
3.消息常規轉發
若是不將消息轉發給其餘類的對象,那麼就只能本身進行處理了。若是上述方法返回self的話,會執行-methodSignatureForSelector:方法來獲取方法的參數以及返回數據類型,也就是說該方法獲取的是方法的簽名並返回。若是上述方法返回nil的話,那麼消息轉發就結束,程序崩潰,報出找不到相應的方法實現的崩潰信息。
在+resolveInstanceMethod:返回NO時就會執行下方的方法,下方也是講該方法轉發給SecondClass,以下所示: