ios業務模組間互相跳轉的解耦方案
*此文章需有一點runtime的知識,如果你不瞭解runtime,《快速理解Runtime of Objective-C》:
問題:
一個app通常由許多個模組組成,所有模組之間免不了會相互呼叫,例如一個讀書管理軟體,可能會有書架、使用者資訊、圖書詳情等等模組,從使用者資訊-我讀的書中,可以開啟圖書詳情。而在圖書詳情-所在書架,又可以開啟書架。一般這種需求我們可能會這實現:
/*使用者資訊模組*/ #import "UserViewController.h" #import "BookDetailViewController.h" @implementation UserViewController //跳轉到圖書詳情 + (void)gotoBookDetail { BookDetailViewController *detailVC = [[BookDetailViewController alloc] initWithBookId:self.bookId]; [self.navigationController.pushViewController:detailVC animated:YES]; } @end
專案初期還好,速度快,夠簡單。但是專案發展到一定程度時,問題就來了,每個模組都離不開其他模組,互相依賴粘在一起:
圖1:模組依賴關係,箭頭方向表示依賴,比如:Discover依賴BookDetail
解決方案:
遇到這種情況,最直接的方法就是增加一箇中間件,各個模組跳轉通過中介軟體來管理。這樣,所有模組只依賴這個中介軟體。但是中介軟體怎麼去呼叫其他模組那?好吧,中介軟體又會依賴所有模組。好像除了增加程式碼的複雜度,並沒有真正解決任何問題。
引入中介軟體的程式碼:
/*使用者資訊模組*/ #import "UserViewController.h" #import "Mediator.h” @implementation UserViewController //跳轉到圖書詳情 + (void)gotoBookDetail { [Mediator gotoBookDetail:self.bookId]; } @end /*中介軟體*/ #import “Mediator.h” #import “BookDetailViewController.h" @implementation Mediator //跳轉到圖書詳情 + (void)gotoBookDetail:(NSString *)bookid { BookDetailViewController *detailVC = [[BookDetailViewController alloc] initWithBookId:bookId]; [self.navigationController.pushViewController:detailVC animated:YES]; } @end
引入中介軟體的依賴關係:
圖2:引入中介軟體的依賴關係
有沒有一種方法,可以完美的解決這個依賴關係那?我們希望做到:每個模組之間互相不依賴,並且每個模組可以脫離工程由不同的人編寫、單獨編譯除錯。下面的方案通過對中介軟體的改造,很好的解決了這個問題,解決後的模組間依賴關係如下:
圖3: 比較理想的模組間依賴關係
實現方法:
我們通過一個實際的例子來分析一下。
先看一下目錄結構,對整個工程的組織結構有一個大致的瞭解,然後結合後面的結構圖和每個類的說明、以及工程程式碼,來詳細分析具體實現:
目錄結構:
[CTMediator工程目錄]
|
|-[CTMediator]
| |-CTMediator.h.m
| |-[Categories]
| |-[ModuleA]
| |-CTMediator+CTMediatorModuleAActions.h.m
|
|-[DemoModule]
| |-[Actions]
| | |-Target_A.h.m
| |-DemoModuleADetailViewController.h.m
|
|-AppDelegate.h.m
|-ViewController.h.m
說明:
[CTMediator]
負責跳轉的中介軟體,所有模組間跳轉都通過這個模組來完成。
[DemoModule]
一個例子模組,假設我們要從其他業務(ViewController.h.m)中跳轉到這個業務模組。
在這個demo中,我們的目的是從其他業務(ViewController.h.m中)跳轉到DemoModule業務模組。
所有模組的引用關係如圖:
圖4:demo中個模組的引用關係
由於demo中只是從ViewController.h.m中跳轉到DemoModule模組,所以只需要ViewController.h.m依賴CTMediator,CTMediator到DemoModule模組的呼叫是使用執行時完成了(圖片中的藍線),在程式碼中不需要相護依賴。也就是說,如果一個模組不需要跳轉到其他模組,就不需要依賴CTMediator。
執行時的時序:
圖5:隱藏了模組內實現細節的引用關係
呼叫關係概述:
首先由ViewController.h.m發起呼叫請求給CTMediator,CTMediator通過runtime去呼叫目標模組DemoModule,目標模組DemoModule根據引數建立自己的一個例項,並把這個例項返回給CTMediator,CTMediator在把這個例項返回給ViewController.h.m(此時ViewController.h.m不需要知道這個例項的具體型別,只需要知道是UIViewController的子類),然後由ViewController.h.m決定以什麼樣的方式去展示DemoModule。
圖6: 完整的呼叫關係
呼叫關係詳解:
1: ViewController.m發起呼叫請求給CTMediator(CTMediator+CTMediatorModuleAActions.m)。ViewController.m-57行
UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForDetail];
2: CTMediator+CTMediatorModuleAActions.m通過定義好的引數呼叫CTMediator,由於CTMediator+CTMediatorModuleAActions是CTMediator的擴充套件,所以可以直接使用self來呼叫CTMediator的實現。CTMediator+CTMediatorModuleAActions.m-行23
UIViewController *viewController =
[self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativFetchDetailViewController
params:@{@"key":@"value"}];
3: CTMediator根據CTMediator+CTMediatorModuleAActions.m傳過來的目標和引數發起實際呼叫。這個呼叫關係是在執行時完成的。所以此處並不需要在程式碼上依賴被呼叫者,如果被呼叫者不存在,也可以在執行時進行處理。CTMediator.m-93行
return [target performSelector:action withObject:params];
4/5: Target_A建立一個DemoModuleADetailViewController型別的例項(這個例項是Target_A通過DemoModuleADetailViewController類的alloc/init建立的)。Target_A.m-20行
DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
6: Target_A返回建立的例項到CTMediator.m(發起時是通過runtime,步驟3),返回後CTMediator.m並不知道這個例項的具體型別,也不會對這個類進行任何解析操作,所以CTMediator.m跟返回的例項之間是沒有任何引用關係的。Target_A.m-23行
7: CTMediator.m返回步驟6中得到的例項到CTMediator+CTMediatorModuleAActions.m(發起時是步驟2)。CTMediator.m-93行
8: CTMediator+CTMediatorModuleAActions.m會將步驟7返回的例項當作UIViewController處理,接下來會在執行時判斷這個例項的型別是不是UIViewController(是不是UIViewController的子類)。然後將得到的UIViewController交給呼叫者ViewController.m(由ViewController.m負責以何種方式進行展示)。CTMediator+CTMediatorModuleAActions.m-行29
所有類的功能如下:
CTMediator.h.m
功能:指定目標(target,類名)+動作(action,方法名),並提供一個字典型別的引數。CTMediator.h.m會判斷target-action是否可以呼叫,如果可以,則呼叫。由於這一功能是通過runtime動態實現的,所以在CTMediator.h.m的實現中,不會依賴任何其他模組,也不需要知道target-action的具體功能,只要target-action存在,就會被執行(target-action具體的功能由DemoModule自己負責)。
CTMediator.h裡實際提供了兩個方法,分別處理url方式的呼叫和target-action方式的呼叫,其中,如果使用url方式,會自動把url轉換成target-action。
CTMediator+CTMediatorModuleAActions.h.m
功能:CTMediator的擴充套件,用於管理跳轉到DemoModule模組的動作。其他模組想要跳轉到DemoModule模組時,通過呼叫這個類的方法來實現。
但是這個類中,並不真正去做跳轉的動作,它只是對CTMediator.h.m類的封裝,這樣使用者就不需要關心使用CTMediator.h.m跳轉到DemoModule模組時具體需要的target名稱和action名稱了。
‘CTMediator.h.m’+‘CTMediator+CTMediatorModuleAActions.h.m’共同組成了一個面相DemoModule的跳轉,並且它不會在程式碼上依賴DemoModule,DemoModule是否提供了相應的跳轉功能,只體現在執行時是否能夠正常跳轉。至此,CTMediator這個中間層實現了完全的獨立,其他模組不需要預先註冊,CTMediator也不需要知道其他模組的實現細節。唯一的關聯就是需要在‘CTMediator+CTMediatorModuleAActions.h.m’中寫明正確的target+action和正確的引數,而且這些action和引數只依賴於Target_A.h.m。action和引數的正確性只會在執行時檢查,如果target或action不存在,可以在‘CTMediator.h.m’中進行相應的處理。既:CTMediator不需要依賴任何模組就可以編譯執行。
Target_A.h.m
提供了跳轉到DemoModule模組的對外介面,與CTMediator+CTMediatorModuleAActions.h.m相互對應,可以說它只用來為CTMediator+CTMediatorModuleAActions.h.m提供服務,所以在實現CTMediator+CTMediatorModuleAActions.h.m時只需要參考Target_A.h.m即可,足夠簡單以至於並不需要文件來輔助描述。其他模組想跳轉到這個模組時,不能直接通過Target_A.h.m實現,而是要通過CTMediator+CTMediatorModuleAActions.h.m來完成。這樣,就實現了模組間相互不依賴,並且只有需要跳轉到其他模組的地方,才需要依賴CTMediator。
DemoModuleADetailViewController.h.m
DemoModule模組的主檢視,這個例子中,會從ViewController.h.m跳轉到這個模組。
AppDelegate.h.m
APP入口,從應用外通過Scheme跳入程式時會經過這個類。
ViewController.h.m
APP主檢視,需要在這裡跳轉到DemoModule模組。
@轉載請包涵以下所有資訊
參考資料:
感興趣的前往閱讀原文。
歡迎大家關注我:iDevShare
或加我微信:lofocus