RxSwift Runtime分析(利用OC消息轉發實現IOS消息攔截)<原理同ReactiveCocoa>

簡要介紹:這是一篇介紹IOS消息攔截的文章,來源于對RxSwift源碼的分析,其原理是利用Object-c的消息轉發(forwardInvocation:)來實現(ReactiveCocoa中也是這個原理,而且是RXSwift借鑒的RAC和MAZeroingWeakRef),閱讀本文章需要對OC的runtime有一定的了解,并且對函數式響應編程(FRP)框架RAC或者RxSwift有一定的了解,不然會很迷惑的。

特別說明:如果想深入到代碼層次來理解這個機制,可以參照著RxSwift框架中RxCocoa模塊代碼的_RXObjCRuntime.h和_RXObjCRuntime.m文件來理解,使用端的代碼可以參考RxCocoa中的NSObject+Rx.swift文件的publicfuncrx_sentMessage(selector:Selector) ->Observable<[AnyObject]>方法(以及publicvarrx_deallocating:Observable<()>),并配合著文中提供的那張大圖來仔細研讀。


本文結構:文章總共包括兩個部分:1)理論部分;2)源碼剖析部分。這兩個部分都會配置上一些圖,幫助大家理解,最后會把用OmniGraffle繪制的圖上傳上來。

1. 理論部分:

至于什么是函數式相應編程(FRP)以及有什么好處此處不做介紹,ReactiveCocoa和RxSwift(結合MVVM)的基本用法也不做介紹,下面只結合RXSwift源碼對這倆框架中的其中一個技術做剖析,也就是本文的主題,如何利用Object-c的消息轉發(forwardInvocation:)機制來實現IOS的消息攔截機制!

a) 首先介紹下最終實現的效果,就是可以為任何OC類(排除CF類和已經實現類似KVO機制的類)的方法添加一個切面(AOP),這個切面可以獲取到原方法的參數列表,加上自己的邏輯,這時就可以做到OC在執行某一個方法的時候,加上自己的邏輯,這一點很想Java中spring的AOP,不過沒那么強大,在RXSwift中主要利用這個工作來給使用者提供一個鉤子(hook),并結合RXSwift中信號流的概念,在方法被調用時,以數據流的方式發送出來。

b) 在繼續下面之前,有必要先簡要說說apple實現KVO的原理(先推薦大家看一下Mike Ash的一篇KVO的文章,還有Mike Ash的一篇NSNotificationCenter的文章,也可以看看NSNotificationCenter 與 KVO 的實現比較的一篇文章),我簡要描述下,KVO的實現,是借助了OC的動態性語言的特點,利用runtime在程序運行時動態的為ClassA添加一個子類,暫且叫_KVO_ClassA,把ClassA設置為其父類,重寫要監聽的屬性pro的setPro方法,在重寫的這個方法中,賦值前后發出相應的通知,并且把用戶當前要監聽的對象的isa指針設置為_KVO_ClassA,這樣,當使用者調用a.pro=xxx的時候,就會順著isa指針找到_KVO_ClassA類的被重寫的setPro方法,這樣就做到了KVO的特性(KVO中還重寫了class函數,使得當使用者調用[a class]時返回的還是ClassA,而不是a的isa指針指向的_KVO_ClassA,這里有點欺騙了使用者)。


c)這里的原理跟這個有一點相似的地方,就是也是通過給當前類ClassB(假設有有一個方法叫selector)添加一個子類_RX_namespace_ClassB(_RX_namespace_是命名前綴),這個類繼承ClassB,并且添加一個_RX_namespace_selector方法,并且把_RX_namespace_selector的實現設置為原selector的實現,然后再把原selector的實現設置為_objc_msgForward,_objc_msgForward是runtime的消息轉發環節的入口,這樣當使用者調用[a selector]是,就進入了OC runtime的消息轉發環節;

d) 這里還有一個地方就是,會為每一個rx臨時類(暫且把_RX_namespace_ClassB這一群類叫做rx臨時類,把_RX_namespace_selector一類的方法叫做rx臨時方法)新增或者替換forwardInvocation:的實現(其實還有另外三個,不過這個最重要,其他三個是respondsToSelector:, class, methodSignatureForSelector: ;其中class就是類似KVO的那種欺騙使用者的效果),在forwardInvocation:的新實現中,調用static BOOL RX_forward_invocation(id self, NSInvocation *invocation)方法;

e) 在進入整個環節之前(也就是開啟監聽方法的入口),還有一個步驟,就是給原類實例對象添加一個關聯屬性,這個關聯屬性的key就是_RX_namespace_selector,屬性值value是名為MessageSentObservable實例對象的鉤子,這樣在d步驟中RX_forward_invocation的實現中以key為_RX_namespace_selector獲取這個鉤子,獲取到鉤子之后,調用鉤子的-(void)messageSentWithParameters:(NSArray*)parameters方法,把原方法的參數以數組的方式傳遞出去,最后在把invocation的方法SEL設置為_RX_namespace_selector,調用[invocation invokeWithTarget:self],還記得d中的方法實現替換的步驟嗎?那一步,把_RX_namespace_selector的實現設置為原方法的實現,這樣,就實現了不破壞現場的特性了;至此通用情形下得原理就結束了。


f) 因為OC的消息轉發環節,不是直接調用方法的實現,而是繞了一個圈子,所以效率上肯定是有折扣的,所以RXSwift中間又加了一層優化機制,下面簡述下優化機制過程原理:

g) 再講述優化機制前,有必要簡要說下RXSwift中強大的宏的運用(這里宏的運用是為了生成函數或者category,從而減少代碼的量),這里到處使用到了宏,大部分都是利用宏來動態生成代碼,比如說生成函數,生成category等等;比如說d中提到的“新增或者替換forwardInvocation:”的方法-(BOOL)swizzleForwardInvocation:(Class __nonnull)class error:(NSError **__nonnull)error就是下面這個宏生成的:

(這個宏生成的category會生成一個方法-(BOOL)swizzle_void_id:(Class __nonnull)class selector:(SEL)selector error:(NSError ** __nonnull)error)

下面優化環節要用到的category也都是宏生成的,文章后面的圖中,會給出例子;在每一個category中,都有load方法,load方法又都是在類被加載時就執行,舉例子來說吧,宏SWIZZLE_OBSERVE_METHOD(void, id)針對的是為返回值為void,有一個參數為id類型的方法簽名的一類生成個RXInterceptWithOptimizedObserver,在load方法中,將這個observer根據方法的簽名為key添加到optimizedObserversByMethodEncoding中(這倆東西h點會講解到);

h) RxSwift中存在一個全局靜態變量optimizedObserversByMethodEncoding,這個變量就是存儲RXInterceptWithOptimizedObserver列表,而RXInterceptWithOptimizedObserver的定義是typedef BOOL (^RXInterceptWithOptimizedObserver)(RXObjCRuntime * __nonnull self, Class __nonnull class, SEL __nonnull selector, NSError ** __nonnull error);,當需要監聽某一個方法的執行時,首先會根據這個方法的方法簽名,到optimizedObserversByMethodEncoding中查找,找不到就進入c,d,e三個對應的通用消息轉發環節中,找到了,就執行g中宏生成的swizzle_void_id方法,這個方法中是將原方法新增或者替換一個block生成的方法實現,這個block中,首先根據_RX_namespace_selector來找RXMessageSentObserver鉤子對象,獲取到鉤子之后,調用鉤子的-(void)messageSentWithParameters:(NSArray*)parameters方法,再調用msgSend(&superInfo, selector, id_0)或者originalImplementationTyped(self, selector, id_0)來保持現場完整性,這樣就把selector給攔截了,別切不破壞現場,而且也沒有用到OC的消息轉發,只有第一次使用時需要方法實現的替換,后續的效率肯定高。

i) 當需要監聽一個對象的dealloc方法的調用時,RXSwift中還針對dealloc方法做了特殊化處理,這個過程跟“優化環節”很像,在此不再表述,文章后面的圖中有說明。

2. 源碼剖析部分:

a) 上面一大堆的原理過程,看著難免有些枯燥,下面給出一張大圖(小弟文檔工地太差,畫圖很不規范,大家就湊合著看吧,意思應該還是表達清楚了),這個圖中有對源碼中關鍵部分,比如說宏,關鍵方法等做解讀(這個圖片太大,下載下來時出現模糊的情況,我另外在github上存了一份,可以去那兒下載高清版,以及OMniGraffe格式的文件,地址,另外,手機簡書APP打開的圖片是高清的,保存下來放在電腦上,效果不錯)

b) 針對宏,有必要給出兩個展開的過程例子,那就選“理論部分中提到的兩個關鍵宏”,其他的宏都是類似展開(展開過程就不詳細寫了,因為寫起來,排版很難看,展開過程也很繁瑣,有興趣的可以私聊),下面只給出幾個宏展開之后的結果:

替換轉發方法forwardInvocation:的實現的宏:

SWIZZLE_OBSERVE_METHOD(void,id)宏(兩次截屏):

備注:這里說的RXSwift其實嚴格來說是RXSwift中的RxCocoa子框架;

關于RxSwift中把Delegate代理、KVO,Notification轉化為流的原理后續文章會給出,這些相對于本文章中原理稍簡單些。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • ———————————————回答好下面的足夠了---------------------------------...
    恒愛DE問候閱讀 1,749評論 0 4
  • 概述 RxSwift顧名思義是Swift的一種框架,您或許曾經聽說過「響應式編程」(Reactive Progra...
    Mr大喵喵閱讀 1,886評論 3 4
  • 文中的實驗代碼我放在了這個項目中。 以下內容是我通過整理[這篇博客] (http://yulingtianxia....
    茗涙閱讀 939評論 0 6
  • 前言 看了前幾篇關于RxSwift主要概念的文章,會對RxSwift有個大致的了解,這篇文章會詳細講述如何使用Rx...
    最Fly的Engine人閱讀 13,065評論 3 40
  • 時間總是在無意間從指縫中流逝,不知不覺你已經是高中畢業邁進自己的大學圣殿,高考之前,我們緊繃的弦也隨高考最后一晚上...
    37度的妳閱讀 1,426評論 3 1