原文:http://reviewcode.cn/article.html?reviewId=20
圍觀神仙打架,反革命工程師《iOS應用架構談 組件化方案》和蘑菇街Limboy的《蘑菇街 App 的組件化之路》的閱讀指導
最近質量最高的文章應該就是上述3篇和Bang關於三篇的解析文章。
地址分別爲:
《iOS應用架構談
組件化方案》
《蘑菇街
App 的組件化之路》
《蘑菇街
App 的組件化之路·續》
《iOS
組件化方案探索》 from Bang
我的文章只是就一些技術細節做一些分析。 畢竟我不是架構師,很多東西我屬於大概知道如何實現,但是我不明白這樣做到底爲什麼。畢竟層次有差距。
第一部分:名詞解釋
問題一:何爲利用URL的方式打開ViewController?
這是入門問題。如果這個問題看不懂,看這兩篇基本等同於看天書。
簡單舉個例子。
首先,看一下一個Node.js服務端如何處理一個get請求。http://weibo.com/u/1438670852/home?topnav=1&wvr=6
這是個登錄之後的微博的首頁URL。其實服務端拿到這個url之後可以利用一些中間件,例如bodyparser之類的把url解析了。比如。你可以這樣獲得topnav的值和wvr的值。
app.get('http://weibo.com/u/:userid/home', function(req, res) {
var topnav = req.body["topnav"];
var wvr = req.body["wvr"];
});
那麼,其實蘑菇街的組件化方案的其中一個功能就是仿照服務端解析URL的方式來處理ViewController之間的跳轉。
蘑菇街是怎麼做的?
-
註冊Class。在
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
方法中先使用單例MGJRouter
註冊所有的ViewController的Pattern,也即VC的模板,你可以想象成是一個字符串對應一個Block,這個block裏面包含了生成ViewController的代碼和需要的賦值的參數。比如上述的DetailViewController可以描述成這樣。
[MGJRouter registerURLPattern:@"mgj://detail?name=:name&summary=:summary" toHandler:^(NSDictionary *routerParameters) {
NSString *name = routerParameters[@"name"];
NSString *summary = routerParameters[@"summary"];
// create view controller with id
// push view controller
}];
這樣註冊了之後,這個MGJRouter單例裏就儲存了這個ViewController.單例裏無非就是有一個可變字典的property,
比如有一個NSMutableDictionary *routers
,然後呢。[[routers setObject:block forKey:url];]
這樣就註冊了。
那麼,怎麼用?
既然有了存的方法,那自然有取得方法。
比如。[MGJRouter openURL:@"mgj://detail?name=:name&summary=:summary"
withParam:@{@"name": @"zql", @"summary":@"wuyanzu" }];
這樣,只需要在字典裏通過URL取得block,然後將參數填進去就行了。 如果還是不理解看這個開源庫。
routable-ios
問題二:什麼是上下文
?
幾篇文中都大量的出現了上下文
這個關鍵字。上下文到底是什麼? 上下文對應的英文單詞是Context。對此比較好理解解釋的是輪子哥的下面這段話。
每一段程序都有很多外部變量。只有像Add這種簡單的函數纔是沒有外部變量的。一旦你的一段程序有了外部變量,這段程序就不完整,不能獨立運行。你爲了使他們運行,就要給所有的外部變量一個一個寫一些值進去。這些值的集合就叫上下文。
看代碼。
DetailViewController *detailVC = [DetailViewController alloc] init];
detailVC.name = @"xxx";
detailVC.summary = @"xxx";
[XX.navigationController detailVC];
這就叫沒有剝離上下文。
why?
因爲你爲了使這個頁面跳轉,你必須要給DetailVC傳一些值。傳的這些值就構成了上下文。
大家注意這段話。
然而這款方案有一個很小的缺陷在於對param的key的hardcode,這是爲了達到最大限度的解耦和靈活度而做的權衡。在我的網絡層架構和持久層架構中,都沒有hardcode的場景,這也從另一個側面說明了組件化架構的特殊性。
我爲什麼要把反革命工程師裏總結的這句話貼出來,因爲這句話很重要。後面會提到。
問題三:什麼叫去Model化?
舉個例子。有個ModelA
ModelA *model = [[ModelA alloc] init];
ViewControllerA *a = [[ViewControllerA alloc] init];
a.model = model;
[self.navigationController pushViewController:a animated:YES];
所以從當前頁面跳轉到a這個控制器的時候需要傳入一個ModelA的實例。那麼如果ModelA因爲業務邏輯的變更,有一個屬性沒了,或者改名字了,或者加了什麼property。
那麼,你就需要在所有的工程文件裏凡是引用了這個Model的文件裏都check一遍。防止發生問題。這就沒有解耦。
爲了防止這種情況,我們在這種情況下,就需要去Model化(那Model還有什麼用?)。 比如你的ViewControllerA需要傳入5個值。那你就老老實實的別傳Model。一個個傳值。
第二部分:流程
理解
大家注意看反革命工程師的Demo.地址
Demo大概架構如下。
ok,現在大腦放空,我們來按流程走一遍。
我們來拿下廚房舉例。
首先如圖所示。我們到了這個頁面。
我們姑且起名字叫做WeekPopularViewController
.
然後,點擊任意一個Cell,我們會跳轉到這個頁面。
我們給這個頁面起名叫RecipeDetailViewController
.
那麼,我們現在按照反革命工程師設計的架構來寫一遍這個流程。
1.創建Target-Action,我們創建一個Target_RecipeDetail
.在這個文件裏,我們主要生成我們的RecipeDetailViewController
和爲我們生成的VC進行一些必要的賦值。例如。
- (UIViewController *)Action_RecipeDetailViewController:(NSDictionary *)params
{
RecipeDetailViewController *viewController = [[RecipeDetailViewController alloc] init];
viewController.valueLabel.text = params[@"key"];
return viewController;
}
2.創建CTMediator的Category.CTMediator+CTMediatorRecipeDetailActions
.這個Category利用Runtime調用我們剛剛生成的Target_RecipeDetail
,注意這個Category神奇的地方,由於利用了Runtime,導致我們完全不用#import
剛剛生成的Target即可執行裏面的方法,所以這一步,兩個類是完全解耦的。也即是說,我們在完全解耦的情況下生成了我們需要的ViewController。例如。
- (UIViewController *)CTMediator_viewControllerForRecipeDetail
{
UIViewController *viewController = [self performTarget:kCTMediatorTargetA
action:kCTMediatorActionNativFetchDetailViewController
params:@{@"key":@"value"}];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之後,可以由外界選擇是push還是present
return viewController;
} else {
// 這裏處理異常場景,具體如何處理取決於產品
return [[UIViewController alloc] init];
}
}
3.由於在Target中,傳遞值得方式採用了去Model化得方式,導致我們在整個過程中也沒有#import任何Model。所以,我們的每個類都與Model解耦。
4.那麼,跳轉的操作也就順理成章的可以使用類似於
UIViewController *viewController = [[CTMediator sharedInstance] CTMediator_viewControllerForRecipeDetail];
[self presentViewController:viewController animated:YES completion:nil];
這樣的方法執行。
注意了,整個流程你居然只需要在WeekPopularViewController
import一個CTMediator+CTMediatorRecipeDetailActions
。
這就是牛逼的地方了。
我看到這裏簡直想大喊wocao,牛逼
。
這裏其實唯一的問題就是反革命工程師自己提出來的,Target_Action裏不得不填入一些Hard Code。就是對創建的VC的賦值語句。因爲想想一下,二次接手的人可能並不知道新創建的VC需要賦哪些值,這就造成了可能會在填入參數列表的時候出錯。 或許還有人看的懵懵懂懂。
那麼我提一個問題。
如果現在上頭有個任務,需要你把項目中的任何一個VC都封裝成這樣。
隨便拎出來一個ViewController,它自帶體系。不需要#import任何別的VC,最多也就是import少量的Model就能單個使用。
你應該怎麼做?
帶着這個問題看,估計會好理解一點。
我寫這篇文章是灰常灰常痛苦的。
爲啥,因爲層次不夠。看了不下20邊,很多都屬於知其然不知其所以然。其實看到現在我感覺也就理解了30%不到的樣子,架構師這個職位確實牛逼。
術和勢的區別啊。