iOS 組件化學習

原文: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之間的跳轉。
蘑菇街是怎麼做的?

  1. 註冊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%不到的樣子,架構師這個職位確實牛逼。
術和勢的區別啊。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章