iOS組件化及架構設計(轉(zhuǎn))

一篇開源代碼的組件化方案

關于組件化

網(wǎng)上組件化的文章很多胶征。很多文章一提到組件化塞椎,就會說解耦,一說到解耦就會說路由或者runtime睛低。好像組件化 == 解耦 == 路由/Runtime案狠,然而這是一個非常錯誤的觀念。持有這一觀點的人钱雷,沒有搞清楚在組件化中什么是想要結(jié)果骂铁,什么是過程。

組件化和解耦

大家不妨先思考兩個問題:

1罩抗、為何要進行組件化開發(fā)拉庵?

2、各個組件之間是否一定需要解耦套蒂?

采用組件化钞支,是為了組件能單獨開發(fā)茫蛹,單獨開發(fā)是結(jié)果。要讓組件能單獨開發(fā)烁挟,組件必須職責單一婴洼,職責單一需要用到重構和解耦的技術,所以重構和解耦是過程撼嗓。那解耦是否是必須的過程柬采?不一定。比如UIKit且警,我們用這個系統(tǒng)組件并沒有使用任何解耦手段警没。問題來了,UIKit蘋果可以獨立開發(fā)振湾,我們使用它為什么沒用解耦手段?答案很簡單亡脸,UIKit沒有依賴我們的代碼所以不用解耦押搪。

PS:我這里不糾結(jié)組件、服務浅碾、模塊大州、框架的概念,網(wǎng)上對這些概念的定義五花八門垂谢,實際上把簡單的事說復雜了厦画。我這里只關心一件事,這一部分代碼能否獨立開發(fā)滥朱,能就叫組件根暑,不能我管你叫什么

我們之所以要解耦才能獨立開發(fā),通常是出現(xiàn)了循環(huán)依賴徙邻。這時候當然可以無腦的用路由把兩個組件的耦合解開排嫌,也可以獨立開發(fā)。然而缰犁,這樣做只是把強引用改成了弱引用淳地,代碼還是爛代碼。站在重構的角度來說帅容,A颇象、B組件循環(huán)依賴就是設計有問題,要么應該重構A并徘、B讓依賴單向遣钳;要么應該抽離一個共用組件C,讓A饮亏、B組件都只依賴于C耍贾。

image

如果我們每個組件都只是單向依賴其他組件阅爽,各個組件之間也就沒有必要解耦。再換個角度說荐开,如果一個組件職責不單一付翁,即使跟其他組件解耦了,組件依然不能很好的工作晃听。如何解耦只是重構過程中可選手段百侧,代碼設計的原則如依賴倒置、接口隔離能扒、里氏替換佣渴,都可以指導我們寫出好的組件。

所以在組件化中重要的是讓組件職責單一初斑,職責單一的重要標志之一就是沒有組件間的循環(huán)依賴辛润。

架構圖

一般來講,App的組件可以分為三層见秤,上層業(yè)務組件砂竖、中層UI組件、底層SDK組件

同一層之間的組件互相獨立鹃答,上層的組件耦合下層的組件乎澄。一般來講,底層SDK組件和中層UI組件都是獨立的功能测摔,不會出現(xiàn)同層耦合置济。

image

業(yè)務組件解耦

上層業(yè)務組件之間的解耦,采用依賴注入的方式實現(xiàn)锋八。每個模塊都聲明一個自己依賴的協(xié)議浙于,在App集成方里去實現(xiàn)這些協(xié)議。

image

我之前的做法是每個模塊用協(xié)議提供自己對外的能力查库,其他模塊通過協(xié)議來訪問它路媚。這樣做雖然也可以解耦,但是維護成本很高樊销,每個模塊都要去理解其他模塊整慎。同時也引入了其他模塊自己用不到的功能,不符合最小依賴的原則围苫。

使用依賴注入裤园,APP集成方統(tǒng)一去管理各個模塊的依賴,每個模塊也能單獨編譯剂府,是業(yè)務層解耦的最佳實踐拧揽。

包管理

要解除循環(huán)依賴,引入包管理技術cocoapods會讓我們更有效率。pod不允許組件間有循環(huán)依賴淤袜,若有pod install時就會報錯痒谴。

cocoapods,提供私有pod repo铡羡,使用時把自己的組件放在私有pod repo里积蔚,然后在Podfile里直接通過pod命令集成。一個組件對應一個私有pod烦周,每個組件依賴自己所需要的三方庫尽爆。多個組件聯(lián)合開發(fā)的時候,可以再一個podspec里配置子模塊读慎,這樣在每個組件自己的podspec里漱贱,只需要把子模塊里的pod依賴關系拷貝過去就行了。

在多個組件集成時會有版本沖突的問題夭委。比如登錄組件(L)幅狮、廣告組件(A)都依賴了埋點組件(O),L依賴O的1.1版本株灸,A依賴O的1.2版本彪笼,這時候集成就會報錯。為了解決這個錯誤蚂且,在組件間依賴時,不寫版本號幅恋,版本號只在APP集成方寫杏死。即podfile里引用所有組件,并寫上版本號捆交,.podspec里不寫版本號淑翼。

這樣做既可以保證APP集成方的穩(wěn)定性,也可以解決組件依賴的版本沖突問題品追。這樣做的壞處是玄括,所有組件包括App集成方,在使用其他組件時肉瓦,都必須使用其他組件最新的API遭京,這會造成額外的升級工作量。如果不想接受組件升級最新api的成本泞莉,可以私有化一個三方庫自己維護哪雕。

組件開發(fā)完畢后告訴集成方,目前的組件穩(wěn)定版本是多少鲫趁,引用的三方庫穩(wěn)定版本集成方自己去決定

image

另一種版本管理的方式斯嚎,是在podspec里寫依賴組件的版本號,podfile里不寫組件依賴的版本,然后通過內(nèi)部溝通來解決版本沖突的問題堡僻。我認為雖然也能做糠惫,但有很多弊端。

1.作為App集成方钉疫,沒辦法單獨控制依賴的三方庫版本苍凛。三方庫升級會更復雜

2.每個依賴的三方庫,都應該做了完整的單元測試访圃,才能被集成到App中肴甸。所以正確的邏輯不是組件內(nèi)測試過三方庫沒問題就在組件內(nèi)寫死版本號,而是這個三方庫經(jīng)過我們測試后咨油,可以在我們系統(tǒng)中使用XX版本您炉。

3.在工程中就沒有一個地方能完整知道所有的pod組件,而App集成方有權利知道這一點

4.溝通成本高

image

順便說一句役电,基礎組件庫可以通過pod子模塊單獨暴露獨立功能赚爵,較常用。

以上法瑟,就是組件化的所有東西冀膝。你可能會奇怪,解耦在組件化過程中有什么用霎挟。答案是解耦是為了更好的實現(xiàn)組件的單一職責窝剖,解耦的作用在架構設計中談。需要再次強調(diào)酥夭,組件化 ≠ 解耦赐纱。

如果非要給組件化下一個定義,我的理解是:

組件化意味著重構熬北,目的是讓每個組件職責單一疙描。在結(jié)構上,每個組件都最小依賴它所需要的東西讶隐。


關于架構設計

在我看來起胰,iOS客戶端架構主要為了解決兩個問題,一是解決大型項目分組件開發(fā)的效率的問題巫延,二是解決單進程App的穩(wěn)定性的問題效五。

設計到架構設計的都是大型App,小型App主要是業(yè)務的堆疊炉峰。很多公司在業(yè)務初期都不會考慮架構火俄,在業(yè)務發(fā)展到一定規(guī)模的時候,才會重新審視架構混亂帶來的開發(fā)效率和業(yè)務穩(wěn)定性瓶頸讲冠。這時候就會引入組件化的概念瓜客,我們常常面臨的是對已有項目的組件化,這一過程會異常困難。

組件拆分原則

對老工程的組件拆分谱仪,我的辦法是玻熙,從底層開始拆。SDK> 模塊 > 業(yè)務 疯攒。如果App沒有SDK可以抽離嗦随,就從模塊開始拆,不要為了抽離SDK而抽離敬尺。常見的誤區(qū)是枚尼,大家一拿到代碼就把公共函數(shù)提出來作為共用框架,起的名字還特別接地氣砂吞,如XXCommon署恍。

事實上,這種框架型SDK蜻直,是最雞肋的組件盯质,原因是它實用性很小,無非就是減少了點冗余代碼概而。而且在架構能力不強的情況下呼巷,它很容易變成“垃圾堆”,什么東西都想往里面放赎瑰,后面越來越龐大王悍。所以,開始拆分架構的時候餐曼,盡量以業(yè)務優(yōu)先配名,比如先拆分享模塊。

如果兩個組件中有共同的函數(shù)晋辆,前期不要想著提出來,改個名字讓它冗余是更好的辦法宇整。如果共同耦合的是一個靜態(tài)庫瓶佳,可以利用動態(tài)庫的隔離性封裝靜態(tài)庫,具體方法可以網(wǎng)上找鳞青。

響應式

基礎組件常常要在系統(tǒng)啟動時初始化霸饲,或者接受App生命周期時間。這就引出了個問題臂拓,如何給appDelegate瘦身厚脉?比如我們現(xiàn)在有兩個基礎組件A、B胶惰,他們都需要監(jiān)聽App生命周期事件傻工,傳統(tǒng)的做法是,A、B兩個組件都提供一些函數(shù)在appDelegate中調(diào)用中捆。但這樣做的壞處是鸯匹,如果某一天我不想引入B組件了,還得去改appDelegate代碼泄伪。理想的方式是殴蓬,基礎組件的使用不需要在appDelegate里寫代碼

為了實現(xiàn)基礎組件與appDelegate分離,得對appDelegate改造蟋滴。首先得提出一個觀點染厅,蘋果的appDelegate設計的有問題,它在用代理模式解決觀察者模式的問題津函。在《設計模式》中肖粮,代理模式的設計意圖定義是:為其他對象提供一種代理以控制對這個對象的訪問。反過來看appDelegate你會發(fā)現(xiàn)球散,它大部分代理函數(shù)都沒有辦法控制application尿赚,如applicationDidBecomeActive。applicationDidBecomeActive這種事件常常需要多個處理者蕉堰,這種場景用觀察者模式更適合凌净。而openURL需要返回BOOL值,才需要使用代理模式屋讶。App生命周期事件雖然可以用監(jiān)聽通知獲取冰寻,但用起來不如響應式監(jiān)聽信號方便。

基于響應式編程的思想皿渗,我寫了一個TLAppEventBus斩芭,提供屬性來監(jiān)聽生命周期事件。我并不喜歡龐大的ReactiveObjectC乐疆,所以我通過category實現(xiàn)了簡單的響應式划乖,用戶只需要監(jiān)聽需要的信號即可。在TLAppEventBus里挤土,我默認提供了8個系統(tǒng)事件用來監(jiān)聽琴庵,如果有其他的系統(tǒng)事件需要監(jiān)聽,可以使用擴展的方法仰美,給TLAppEventBus添加屬性(見文末Demo)迷殿。

路由

對于Appdelegate中的openURL的事件,蘋果使用代理模式并沒有問題咖杂,但我們常常需要在openURL里面寫if-else區(qū)分事件的處理者庆寺,這也會造成多個URL處理模塊耦合在Appdelegate中。我認為appdelegate中的openURL應該用路由轉(zhuǎn)發(fā)的方式來解耦诉字。

openURL代理需要同步返回處理結(jié)果懦尝,但網(wǎng)上開源的路由框架能同步返回結(jié)果的知纷。所以我這邊實現(xiàn)了一個能同步返回結(jié)果的路由TLRouter,同時支持了注冊scheme导披。注冊scheme這一特性屈扎,在第三方分享的場景下會比較有用(見文末Demo)。

另外撩匕,網(wǎng)上大部分方案都搞錯了場景鹰晨。以蘑菇街的路由方案為例(好像iOS的路由就是他們提出來的?)止毕,蘑菇街認為路由主要有兩個作用模蜡,一是發(fā)送數(shù)據(jù)讓路由接收者處理,二是返回對象讓路由發(fā)送者繼續(xù)處理扁凛。我不禁想問忍疾,這是路由嗎?不妨先回到URL的定義

URL: 統(tǒng)一資源標識符(Uniform Resource Locator,統(tǒng)一資源定位符)是一個用于標識某一互聯(lián)網(wǎng)資源名稱的字符串

openURL就是在訪問資源谨朝,在瀏覽器中卤妒,openURL意味著打開一個網(wǎng)頁,openURL的發(fā)起者并不關心打開的內(nèi)容是什么字币,只關心打開的結(jié)果则披。所以蘋果的openURL Api 就只返回了除了結(jié)果YES/NO,沒有返回一個對象洗出。所以士复,我對openURL這一行為定義如下

openURL:訪問資源,返回是否訪問成功

那把蘑菇街的路由翩活,返回的對象改成BOOL值就可以了么阱洪?我認為還不夠。對于客戶端的路由菠镇,使用的實際上是通知的形式在解耦冗荸,帶來的問題是路由的注冊代碼散落在各地,所以路由方案必須要配路由文檔利耍,要不然開發(fā)者會不知道路由在干嘛蚌本。

有沒有比文檔更好的方式呢?我的思路是:用schema區(qū)分路由職責

系統(tǒng)的openURL只干了兩件事:打開App和打開網(wǎng)頁

[[UIApplicationsharedApplication] openURL:[NSURLURLWithString:@"weixin://"]]; // 打開App

[[UIApplicationsharedApplication] openURL:[NSURLURLWithString:@"https://www.baidu.com"]];//打開網(wǎng)頁

兩者的共性是頁面切換堂竟。所以我這邊設計的路由openURL,只擴充了controller跳轉(zhuǎn)的功能玻佩,比如打開登錄頁

[TLRouter openURL:@"innerJump://account/login"];

只擴充了controller跳轉(zhuǎn)的功能好處是讓路由的職責更單一出嘹,同時也更符合蘋果對openURL的定義。工程師在看到url schema的時候就知道他的作用咬崔,避免反復查看文檔税稼。

對于數(shù)據(jù)的傳遞烦秩,我認為不應該用路由的方式。相比路由郎仆,通過依賴注入傳入信號是更好的選擇只祠。

App配置

有時候我們需要組件的跨App復用,在App集成組件時扰肌,能夠不改代碼只改配置是最理想的方式抛寝。使用組件+plist配置是一個方案,具體做法是把A組件的配置放在A.plist中曙旭,在A組件內(nèi)寫死要讀取A.plist盗舰。

以配置代替硬編碼,防止對代碼的侵入桂躏,是一個很好的思路钻趋。設想一下,如果我們可以通過配置在決定App是否使用組件剂习、也可通過配置來改變組件和app所需的參數(shù)蛮位,那運維可以代替app開發(fā)來出包,這對效率和穩(wěn)定性都會有提升鳞绕。為了實現(xiàn)這一效果失仁,我使用了OC的runtime來動態(tài)注冊組件。需要在didfinishLaunch初始化的組件猾昆,可以實現(xiàn)代理 - (void)initializeWhenLaunch; 這樣陶因,自動初始化函數(shù),就可以通過runtime+plist里配置的class name自動初始化垂蜗。組件需要初始化的代碼楷扬,可以在自己的initializeWhenLaunch里做。

由于路由只擴充了controller跳轉(zhuǎn)的功能贴见,所以路由注冊這一行為也可進行一次抽象烘苹,把不同的部分放在plist配置文件,相同的放到runtime里做片部。這樣做還有個好處是镣衡,程序內(nèi)的路由跳轉(zhuǎn)在一個plist里可以都可以看到

image

iOS解耦工具Tourelle

Tourelle,是根據(jù)上面的思路寫的一個開源項目 https://github.com/zhudaye12138/Tourelle档悠,可以通過pod集成 pod 'Tourelle'廊鸥。下面介紹一下他的使用方式

TLAppEventBus

TLAppEventBus通過接收系統(tǒng)通知來獲取app生命周期事件,收到生命周期事件后改變對應屬性的值辖所。默認提供了didEnterBackground等八個屬性惰说,可以使用響應式函數(shù)來監(jiān)聽

  • (void)observeWithBlock:(TLObservingBlock)block;

[TLAppEventBus.shared.didBecomeActive observeWithBlock:^(idnewValue) {

    //do some thing

}];

需要注意,如果在其它地方使用observeWithBlock缘回,需要設置屬性的owner吆视,否則沒有辦法監(jiān)聽到典挑。這里不用單獨設置是因為在TLAppEventBus里已設置好

TLAppEventBus使用前需要調(diào)用 - (void)start; 如果需要監(jiān)聽更多的事件,可以調(diào)用

  • (void)startWithNotificationMap:(NSDictionary *)map;

NSMutableDictionary *defaultMap = [NSMutableDictionary dictionaryWithDictionary:[TLAppEventBus defaultNotificationMap]]; //獲取默認map

[defaultMapsetObject:KDidChangeStatusBarOrientation forKey:UIApplicationWillChangeStatusBarOrientationNotification]; //添加新的事件

[TLAppEventBus.shared startWithNotificationMap:defaultMap];//開啟EventBus

添加新事件需要用分類添加TLAppEventBus的屬性啦吧,添加后就可正常使用了

-(void)setDidChangeStatusBarOrientation:(NSNotification*)didChangeStatusBarOrientation {

objc_setAssociatedObject(self, (__bridge const void *)KDidChangeStatusBarOrientation , didChangeStatusBarOrientation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

-(NSNotification*)didChangeStatusBarOrientation {

returnobjc_getAssociatedObject(self, (__bridge const void *)KDidBecomeActive);

}

TLRouter

路由支持兩種注冊方式您觉,一種只寫schema,一種寫url路徑

[TLRouter registerURL:@"wx1234567://" hander:^(TLRouterURL *routeURL, void (^callback)(BOOL result)) {

    //do something     

}]//注冊schema

[TLRouter registerURL:@"InnerJump://account/login" hander:^(TLRouterURL *routeURL, void (^callback)(BOOL result)) {

            //do something

}]//注冊url路徑

支持同步 & 異步獲取返回值授滓,其中異步轉(zhuǎn)同步內(nèi)部通過semaphore實現(xiàn)

+(void)openURL:(NSString*)url callback:(void(^)(BOOLresult))callback;

+(BOOL)openURL:(NSString*)url;

另外openURL除了支持url中帶參數(shù)琳水,也支持參數(shù)放在字典中

+(BOOL)openURL:(NSString*)url param:(NSDictionary *)param;

TLAppLaunchHelper

TLAppLaunchHelper有兩個函數(shù),一個用來初始化組件褒墨。該函數(shù)會讀取AutoInitialize.plist中的classes炫刷,通過runtime + 自動初始化協(xié)議完成初始化

-(void)autoInitialize;

image

另一個函數(shù)用來自動注冊路由,該函數(shù)會讀取AutoRegistURL.plist完成路由注冊郁妈。其中controller代表類名浑玛,params代表默認參數(shù),如果openURL傳的參數(shù)與默認參數(shù)不符合噩咪,路由會報錯

-(void)autoRegistURL;

image

路由注冊時顾彰,并不決定controller跳轉(zhuǎn)的方式。注冊者只是調(diào)用presentingSelf方法胃碾,跳轉(zhuǎn)方式由controller中presentingSelf方法決定涨享。

-(BOOL)presentingSelf {

UINavigationController *rootVC = (UINavigationController *) APPWINDOW.rootViewController;

if(rootVC) {

    [rootVCpushViewController:self animated:YES];

    returnYES;

}

return NO;

}

耦合檢測工具

針對既有代碼的組件化重構,我這邊開發(fā)了一個耦合檢測工具仆百,目前只支持OC厕隧。

耦合檢測工具的原理是這樣:工具認為工程中一級文件夾由組件構成,比如A工程下面有aa俄周、bb吁讨、cc三個文件夾,aa峦朗、bb建丧、cc就是三個待檢測的組件。耦合檢測分三步波势,第一步通過正則找到組件內(nèi).h文件中所有關鍵字(包括函數(shù)翎朱、宏定義和類)。第二步通過找到的組件內(nèi)關鍵字尺铣,再通過正則去其它組件的.m中找是否使用了該組件的關鍵字拴曲,如果使用了,兩個組件就有耦合關系凛忿。第三步澈灼,輸出耦合檢測報告

代碼:開源中....

總結(jié)

本文給出了組件化的定義:組件化意味著重構,目的是讓每個組件職責單一以提升集成效率侄非。包管理技術Pod是組件化常用的工具蕉汪,iOS組件依賴及組件版本號確定,都可以用pod實現(xiàn)逞怨。整個iOS工程的組件通常分為3層者疤,業(yè)務組件、模塊組件和SDK組件叠赦。在老工程重構時驹马,優(yōu)先抽離SDK組件,切記不要寫XXCommon讓它變成垃圾堆除秀。

關于解耦的技術糯累,appldegate適合用觀察者模式替換代理模式,路由只用來做controller之間的跳轉(zhuǎn)册踩,上層業(yè)務組件的解耦靠依賴注入而不是全用路由泳姐。工程的組件和路由都可通過runtime + 配置的形式自動注冊,這樣做維護和集成都會很方便暂吉。

Demo地址:https://github.com/zhudaye12138/Tourelle

作者:朱大爺12138
鏈接:http://www.reibang.com/p/d88aef8e29a4
來源:簡書
簡書著作權歸作者所有胖秒,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權并注明出處。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末慕的,一起剝皮案震驚了整個濱河市阎肝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肮街,老刑警劉巖风题,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嫉父,居然都是意外死亡沛硅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門熔号,熙熙樓的掌柜王于貴愁眉苦臉地迎上來稽鞭,“玉大人,你說我怎么就攤上這事引镊‰蹋” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵弟头,是天一觀的道長吩抓。 經(jīng)常有香客問我,道長赴恨,這世上最難降的妖魔是什么疹娶? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮伦连,結(jié)果婚禮上雨饺,老公的妹妹穿的比我還像新娘钳垮。我一直安慰自己,他們只是感情好额港,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布饺窿。 她就那樣靜靜地躺著,像睡著了一般移斩。 火紅的嫁衣襯著肌膚如雪肚医。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天向瓷,我揣著相機與錄音肠套,去河邊找鬼。 笑死猖任,一個胖子當著我的面吹牛你稚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朱躺,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼入宦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了室琢?” 一聲冷哼從身側(cè)響起乾闰,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盈滴,沒想到半個月后涯肩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡巢钓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年病苗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片症汹。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡硫朦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出背镇,到底是詐尸還是另有隱情咬展,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布瞒斩,位于F島的核電站破婆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胸囱。R本人自食惡果不足惜祷舀,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧裳扯,春花似錦抛丽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哟忍,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陷寝,已是汗流浹背锅很。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凤跑,地道東北人爆安。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像仔引,于是被迫代替她去往敵國和親扔仓。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 關于組件化 網(wǎng)上組件化的文章很多咖耘。很多文章一提到組件化翘簇,就會說解耦,一說到解耦就會說路由或者runtime儿倒。好像組...
    朱大爺12138閱讀 20,812評論 17 130
  • 前言: 本文轉(zhuǎn)自前同事casa的博文版保,這篇文章是基于runtime實現(xiàn)的iOS組件化方案,其實iOS組件化方案基本...
    monkey01閱讀 1,662評論 1 2
  • 前因其實我們這個7人iOS開發(fā)團隊并不適合組件化開發(fā)夫否。原因是因為性價比低彻犁,需要花很多時間和經(jīng)歷去做這件事,帶來的收...
    其實也沒有閱讀 722評論 0 3
  • 第86首現(xiàn)代詩凰慈,寫于20171116汞幢,耗時12分 趁北風未至 穿上夏天最后的裙子 嘆時光如詩 匆匆亦太遲 陽臺外的...
    茉莉的小茶館閱讀 147評論 4 5
  • 01 你身邊也有過這樣的姑娘吧豺型。 他們善良溫柔疾宏、工作努力、自立好強触创、懂事獨立坎藐,獨立到可能無需別人來照顧,餓了就去做...
    陌少Sandy閱讀 1,191評論 3 2