自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

設(shè)計(jì)一個(gè)簡(jiǎn)單的iOS架構(gòu)

移動(dòng)開(kāi)發(fā)
正如“100個(gè)讀者就有100個(gè)哈姆雷特”一樣,對(duì)于架構(gòu)的理解不同的軟件工程師有不同的看法。架構(gòu)設(shè)計(jì)往往是一個(gè)權(quán)衡的過(guò)程,每一個(gè)架構(gòu)設(shè)計(jì)者都要考慮到各個(gè)因素,比如團(tuán)隊(duì)成員的技術(shù)水平、具體的業(yè)務(wù)場(chǎng)景、項(xiàng)目的成長(zhǎng)階段和開(kāi)發(fā)周期。

前言

正如“100個(gè)讀者就有100個(gè)哈姆雷特”一樣,對(duì)于架構(gòu)的理解不同的軟件工程師有不同的看法。架構(gòu)設(shè)計(jì)往往是一個(gè)權(quán)衡的過(guò)程,每一個(gè)架構(gòu)設(shè)計(jì)者都要考慮到各個(gè)因素,比如團(tuán)隊(duì)成員的技術(shù)水平、具體的業(yè)務(wù)場(chǎng)景、項(xiàng)目的成長(zhǎng)階段和開(kāi)發(fā)周期。

本文談?wù)劰P者的一些架構(gòu)理念,以及本人是如何設(shè)計(jì)一個(gè)簡(jiǎn)單的 iOS 架構(gòu)。

iOS 架構(gòu) DEMO

一、關(guān)于組件化

組件化似乎是項(xiàng)目發(fā)展壯大過(guò)后必然要做的事情,它能讓各個(gè)業(yè)務(wù)線的工程師不需要過(guò)多的關(guān)注其他業(yè)務(wù)線的代碼,有效的提高團(tuán)隊(duì)整體效率。然而實(shí)施組件化的時(shí)機(jī)是在需求相對(duì)穩(wěn)定、產(chǎn)品閉環(huán)形成過(guò)后。所以本文不會(huì)應(yīng)用組件化,但是這里簡(jiǎn)單談?wù)剺I(yè)界的組件化方案。

組件化的核心問(wèn)題就是組件間如何通訊。“軟件工程的一切問(wèn)題都能通過(guò)一個(gè)間接的中間層解決。”中介模式很自然的運(yùn)用起來(lái):

設(shè)計(jì)一個(gè)簡(jiǎn)單的iOS架構(gòu)

這樣雖然能統(tǒng)一組件間的通訊請(qǐng)求,但是卻沒(méi)有避免 Mediator 和目標(biāo)組件的耦合,ModuleA 工程中仍然需要導(dǎo)入 ModuleB 。

所以重點(diǎn)問(wèn)題落在了解耦上: 

設(shè)計(jì)一個(gè)簡(jiǎn)單的iOS架構(gòu)

要達(dá)到 Mediator 和目標(biāo)組件的解耦,就需要實(shí)現(xiàn)它們之間的間接調(diào)用(圖中虛線),既然是間接調(diào)用,必然需要一種映射機(jī)制。在 iOS 開(kāi)發(fā)中,業(yè)界大概有三種方式來(lái)處理。

(1) 使用 URL -> Block 解耦

簡(jiǎn)單來(lái)說(shuō)就是將組件的調(diào)用代碼放入 block 中,然后 URL 作為 key,block 作為 value,存入一個(gè)全局的 hash 容器,組件通過(guò)一個(gè) URL (比如 "native/id=10/type=1" )向 Mediator 發(fā)起請(qǐng)求,Mediator 找到對(duì)應(yīng)的代碼塊執(zhí)行。由此,解開(kāi)了 Mediator 和目標(biāo)組件的耦合(見(jiàn)博客:蘑菇街 App 的組件化之路)。

這種方案的缺陷很多:組件越多常駐內(nèi)存越多;解析 URL 邏輯復(fù)雜;URL 無(wú)法表述具體語(yǔ)言相關(guān)的對(duì)象類型。所以這種方式并不適合組件化解耦。

(2) 使用 Protocol 解耦

阿里的 BeeHive 是該方案的很好實(shí)踐,筆者閱讀了一下源碼,它的大致工作原理如下:注冊(cè) Protocol 對(duì)應(yīng)的組件,這個(gè)和上面說(shuō)的 URL->Block 方式如出一轍,只不過(guò)這里是 Protocol-> Module ;組件申請(qǐng)?jiān)L問(wèn)時(shí)導(dǎo)入對(duì)應(yīng)的 Protocol 通過(guò) Mediator 獲取到對(duì)應(yīng)的組件對(duì)象。由于協(xié)議的表述能支持所有的對(duì)象類型,所以這種方式能基本解決組件間通信的需求。

BeeHive 注冊(cè)組件有幾種方式,一種是監(jiān)聽(tīng)了動(dòng)態(tài)鏈接時(shí) image 二進(jìn)制文件加載完成的回調(diào),通過(guò)修改代碼段的方式判斷對(duì)應(yīng)的模塊進(jìn)行注冊(cè);第二種是在 +load 方法里面注冊(cè);第三種是異步注冊(cè),但是這種方式存在一個(gè)問(wèn)題,可能組件使用方準(zhǔn)備使用組件的時(shí)候,這個(gè)組件還未注冊(cè)成功。

BeeHive 還為組件設(shè)置了優(yōu)先級(jí)的概念,它通過(guò)數(shù)組來(lái)保持優(yōu)先級(jí)排序,在源碼中能看到一些數(shù)組排序的邏輯,這就帶來(lái)了相當(dāng)多的高時(shí)間復(fù)雜度的運(yùn)算。

所以,組件數(shù)量過(guò)多的話,會(huì)延長(zhǎng)動(dòng)態(tài)鏈接庫(kù)的過(guò)程。

BeeHive 為了讓每一個(gè)組件享有獨(dú)自的 app 生命周期、3D touch 等功能,會(huì)將這些系統(tǒng)級(jí)的事件發(fā)送給每一個(gè)組件,且不談大量的方法調(diào)用損耗,它必須讓入口文件 AppDelegate 繼承自 BeeHive 的 BHAppDelegate,筆者感覺(jué)侵入性過(guò)強(qiáng),并且當(dāng)開(kāi)發(fā)者需要復(fù)寫(xiě) AppDelegate 方法的時(shí)候,還要注意讓super調(diào)用一下,可以說(shuō)很不優(yōu)雅了。

在基于協(xié)議的組件化方案中,組件使用方能直接拿到目標(biāo)組件的實(shí)例,那么使用者可能對(duì)該實(shí)例進(jìn)行修改,這可能會(huì)帶來(lái)安全問(wèn)題。

(3) 使用 Target-Action 解耦

Casa Taloyum 前輩的 iOS應(yīng)用架構(gòu)談 組件化方案 為此做出了***實(shí)踐。

Mediator 使用 Target-Action 來(lái)間接的調(diào)用目標(biāo)組件,無(wú)需專門注冊(cè)。組件維護(hù)者需要做一個(gè) Mediator 的分類,通過(guò)硬編碼調(diào)用目標(biāo)組件,然后組件使用者只需要依賴這個(gè)分類就行了。

封裝的 Mediator 源碼只有簡(jiǎn)單的 200+ 行代碼,并且很易懂。這也讓開(kāi)發(fā)者能對(duì)組件化的實(shí)施更加有信心,不會(huì)因?yàn)榛A(chǔ)設(shè)施的錯(cuò)誤而束手無(wú)策。

小總結(jié)

關(guān)于以上組件化的簡(jiǎn)單表述僅代表筆者的個(gè)人見(jiàn)解,由于筆者并沒(méi)有真正的實(shí)施組件化,所以理解可能有誤。

雖然筆者設(shè)計(jì)的 iOS 架構(gòu)不會(huì)應(yīng)用組件化,但是這給我們的架構(gòu)設(shè)計(jì)帶來(lái)了前瞻性的引導(dǎo),這非常重要。

二、模塊化思維劃分文件

在團(tuán)隊(duì)開(kāi)發(fā)中,項(xiàng)目發(fā)展到后期總是會(huì)出現(xiàn)某些文件或代碼難以管理,出現(xiàn)這種情況的主要原因通常是項(xiàng)目開(kāi)發(fā)過(guò)程中對(duì)文件的管理過(guò)于隨意。

開(kāi)發(fā)者應(yīng)該盡量將所有代碼文件歸于模塊,而不要出現(xiàn)模擬兩可的文件。而筆者這里說(shuō)的模塊,是有具體意義的模塊,比如圖片處理模塊、字體處理模塊,而不是諸如 Public、Common 等無(wú)具體意義的代碼文件。

試想,在多人開(kāi)發(fā)中,當(dāng)所有人都覺(jué)得有些代碼不知道怎么歸類的時(shí)候,就會(huì)往 Public 里面扔。當(dāng)你某天想要整理一下這個(gè) Public,會(huì)發(fā)現(xiàn)已經(jīng)無(wú)從下手;或者當(dāng)你需要遷移項(xiàng)目中的某個(gè)業(yè)務(wù)模塊時(shí),會(huì)附帶遷移一些模塊,當(dāng)這個(gè)模塊是有意義的(比如圖片處理模塊),你的遷移成本會(huì)非常低,但是當(dāng)這個(gè)藕斷絲連的模塊是 Public 時(shí),時(shí)間成本可能高于你的想象,估計(jì)你會(huì)將它完整的拷貝過(guò)去,而又對(duì)新項(xiàng)目造成了污染。

全局的公共文件是產(chǎn)生垃圾代碼的源頭。筆者認(rèn)為幾乎所有的代碼都是可以歸類為模塊的。

大致梳理了一個(gè)文件分類,當(dāng)然這個(gè)分類是靈活的,只是要分模塊劃分:

  • - GeneralModules 放項(xiàng)目獨(dú)有的通用配置模塊(比如通用顏色模塊、通用字體模塊)
  • - ToolModules 放工具類模塊(比如系統(tǒng)信息模塊)
  • - PackageModules 放基于業(yè)務(wù)的一些封裝(比如提示框模塊、加載菊花模塊)
  • - BusinessModules 放業(yè)務(wù)模塊(比如購(gòu)物車、個(gè)人中心)

具體里面放了些什么,可以查看筆者的 DEMO。

三、減少全局宏的使用

很多時(shí)候,過(guò)多的宏讓項(xiàng)目很不整潔,每一個(gè)開(kāi)發(fā)者都往全局文件添加宏,而往往只是一段簡(jiǎn)單的代碼,筆者認(rèn)為開(kāi)發(fā)中應(yīng)該盡量少使用宏,原因如下:

  • 宏在預(yù)編譯階段替換為實(shí)際代碼,存在效率問(wèn)題
  • 使用宏的地方可能只需要一塊內(nèi)存,但是宏替換過(guò)后開(kāi)辟了多個(gè)(這種情況應(yīng)該用常量替換宏)
  • 可能存在潛在的宏命名沖突
  • 宏包裝過(guò)多的代碼難以理解和調(diào)試
  • 代碼遷移時(shí)需要處理全局的宏

實(shí)際上,非得使用宏的地方并非那么多,比如需要定義一個(gè)全局的導(dǎo)航欄字體方便使用,可以將通用字體的配置參數(shù)作為一個(gè)模塊: 

  1. @interface YBGeneralFont : NSObject 
  2. /** 導(dǎo)航欄標(biāo)題字體 */ 
  3. + (UIFont *)navigationBarTitleFont; 
  4. @end 

或者用常量來(lái)代替宏: 

  1. .h 
  2. FOUNDATION_EXTERN NSString * const kNotify_xxx; //xxx通知 key 
  3. .m 
  4. NSString * const kNotify_xxx = @"kNotify_xxx"

這么做也便于轉(zhuǎn)換思維,畢竟 swift 中是沒(méi)有宏的。

四、去基類化設(shè)計(jì)

代碼設(shè)計(jì)中,應(yīng)該盡量避免基類的使用,也就是說(shuō),你不應(yīng)該總是要求開(kāi)發(fā)者去繼承你的基類來(lái)做功能。使用基類將造成不可避免的耦合,為業(yè)務(wù)的長(zhǎng)期發(fā)展帶來(lái)阻礙(當(dāng)然某些情況是可以使用基類的)。

其實(shí)使用基類就算了,若是將大量的業(yè)務(wù)邏輯放入基類中將是災(zāi)難的開(kāi)端。試想,當(dāng)項(xiàng)目新成員一來(lái)就看見(jiàn)成千上萬(wàn)行的基類代碼TA作何感想?

另外一種場(chǎng)景,當(dāng)需要將項(xiàng)目中的某個(gè)模塊遷移到其他項(xiàng)目,或者需要將其他項(xiàng)目合并入當(dāng)前項(xiàng)目,基類的合并將是一個(gè)非常頭疼的問(wèn)題,它藕斷絲連的模塊和代碼會(huì)讓你抓狂。

那么,類的工具方法應(yīng)該放哪兒?對(duì)所有類的統(tǒng)一配置應(yīng)該放哪兒?對(duì)封裝模塊的個(gè)性化定制應(yīng)該怎么做?

裝飾模式

類的工具方法,按道理說(shuō)可以提取為模塊,但是有些場(chǎng)景可能顯得不夠簡(jiǎn)潔。

其實(shí)只要留意 iOS 官方的 API,你就不難發(fā)現(xiàn)裝飾模式的大量應(yīng)用,使用數(shù)個(gè)分類將大量的方法按照功能分類,會(huì)清晰且優(yōu)雅: 

  1. @interface UIViewController (YBGeneral) 
  2. /** 基礎(chǔ)配置 */ 
  3. - (void)YBGeneral_baseConfig; 
  4. @end 
  5. @interface UIViewController (YBGeneralBackItem) 
  6. /** 配置通用系統(tǒng)導(dǎo)航欄返回按鈕 */ 
  7. - (void)YBGeneral_configBackItem; 
  8. /** 重寫(xiě)該方法以自定義系統(tǒng)導(dǎo)航欄返回按鈕點(diǎn)擊事件 */ 
  9. - (void)YBGeneral_clickBackItem:(UIBarButtonItem *)item; 
  10. @end 

不過(guò)要注意的時(shí),定義分類的時(shí)候一定要加一個(gè)前綴標(biāo)識(shí)以避免方法覆蓋。

AOP

面向切面編程在 iOS 領(lǐng)域經(jīng)典的應(yīng)用就是利用 Runtime 去 Hook 方法: 

  1. @implementation UIViewController (YBGeneralHook) 
  2. + (void)load { 
  3.     [self YBGeneralHook_exchangeImplementationsWithOriginSel:@selector(viewDidLoad) customSel:@selector(YBGeneralHook_viewDidLoad)]; 
  4. + (void)YBGeneralHook_exchangeImplementationsWithOriginSel:(SEL)originSel customSel:(SEL)customSel { 
  5.     Method origin = class_getInstanceMethod(self, originSel); 
  6.     Method custom = class_getInstanceMethod(self, customSel); 
  7.     if (origin && custom) { 
  8.         method_exchangeImplementations(origin, custom); 
  9.     } 
  10. - (void)YBGeneralHook_viewDidLoad { 
  11.     NSLog(@"進(jìn)入:%@", self); 
  12.     [self YBGeneral_baseConfig]; 
  13.     if (self.navigationController && [self.navigationController.viewControllers indexOfObject:self] != 0) { 
  14.         [self YBGeneral_configBackItem]; 
  15.     } 
  16.     [self YBGeneralHook_viewDidLoad]; 
  17. @end 

代碼中統(tǒng)一配置了 UIViewController 的系統(tǒng)導(dǎo)航欄返回按鈕,注意這里調(diào)用的業(yè)務(wù)配置方法都是定義在 UIViewController 的分類里面的。若有某些導(dǎo)航欄需要格外配置返回按鈕的需求,可以拓展一個(gè)屬性來(lái)控制。

面向協(xié)議設(shè)計(jì)模式

對(duì)于一些封裝的組件,多考慮使用協(xié)議來(lái)個(gè)性化定制,繼承作為最差方案,而非是***方案。

定義一個(gè)遵守組件定制協(xié)議的屬性是常用的解決方法:

  1. @property (nonatomic, strong) id<someProtocol>  strategy; 

不同的屬性作為不同的策略,組件內(nèi)部通過(guò)調(diào)用對(duì)應(yīng)的協(xié)議方法實(shí)現(xiàn)個(gè)性化定制。而當(dāng)使用者想要改變策略時(shí),只需要更改這個(gè)屬性就行了。面向協(xié)議設(shè)計(jì)模式結(jié)合策略模式是一個(gè)很好的實(shí)踐。

五、MVC?MVP?MVVM?VIPER?

業(yè)務(wù)具體的架構(gòu)模式是個(gè)讓很多開(kāi)發(fā)者頭疼的問(wèn)題,因?yàn)橛袝r(shí)候能讓復(fù)雜業(yè)務(wù)更清晰,有時(shí)候卻因?yàn)槟z水代碼過(guò)多而臃腫。

實(shí)際上為什么要嚴(yán)格的遵守架構(gòu)模式呢?為什么每一個(gè)業(yè)務(wù)模塊的架構(gòu)模式都要一模一樣呢?

筆者認(rèn)為正確的架構(gòu)思路一定是根據(jù)業(yè)務(wù)來(lái)的,不同的模塊,不同的業(yè)務(wù)線完全可以有不同的架構(gòu),只需要架構(gòu)足夠清晰不至于晦澀。

大致設(shè)計(jì)了一下架構(gòu)的主旋律:

設(shè)計(jì)一個(gè)簡(jiǎn)單的iOS架構(gòu)

  • DataCenter 負(fù)責(zé)數(shù)據(jù)的獲取、處理、緩存等。
  • Model 設(shè)計(jì)為“瘦” Model,便于復(fù)用和遷移;也考慮到數(shù)據(jù)源可能數(shù)量龐大,若 Model 設(shè)計(jì)得過(guò)于“胖”,會(huì)造成更多的內(nèi)存占用。
  • View 負(fù)責(zé)數(shù)據(jù)的展示,可以根據(jù)業(yè)務(wù)情況權(quán)衡是否需要 ViewModel 處理界面邏輯。
  • ViewController 作為 DataCenter 和 View 的橋梁。

筆者設(shè)計(jì)的項(xiàng)目目前不會(huì)很復(fù)雜,多數(shù)情況上面的架構(gòu)就已經(jīng)夠用,若某個(gè)頁(yè)面功能過(guò)多,完全可以提取一些額外的模塊,比如 DataCenter 處理過(guò)于復(fù)雜,那就把數(shù)據(jù)的處理和緩存提取出來(lái):xxxDataProcesser、xxxDataCache。這些都是靈活的,只需要按照模塊化的思維提取,ViewController 的代碼相信也不會(huì)太多。

關(guān)于響應(yīng)式框架

Reactivecocoa 雖然強(qiáng)大,筆者以前也用過(guò),不過(guò)它是一個(gè)重量級(jí)框架,學(xué)習(xí)成本有點(diǎn)高,可能會(huì)因?yàn)閳F(tuán)隊(duì)成員對(duì)其了解不足導(dǎo)致難以定位的錯(cuò)誤。

而美團(tuán)的 EasyReact 似乎是一個(gè)福音,筆者大概瀏覽了一下源碼,質(zhì)量確實(shí)很高,對(duì)性能方面的處理很精致,基于圖論算法的處理也感覺(jué)很棒,項(xiàng)目侵入性也很小。不過(guò)缺點(diǎn)就是太新了,需要開(kāi)發(fā)社區(qū)一定時(shí)間的驗(yàn)證,暫時(shí)筆者持觀望態(tài)度。

結(jié)語(yǔ)

本文只是作者思考過(guò)后對(duì)一個(gè)項(xiàng)目架構(gòu)的簡(jiǎn)單設(shè)計(jì),還有很多部分需要完善和補(bǔ)充,具體細(xì)節(jié)也可能會(huì)按照具體情況修改。Demo 只是一個(gè)雛形,希望和各位讀者朋友能有所交流。

 

責(zé)任編輯:未麗燕 來(lái)源: 簡(jiǎn)書(shū)
相關(guān)推薦

2024-04-24 10:38:22

2012-02-01 14:12:55

iOS本地緩存機(jī)制

2019-06-27 09:50:49

高性能秒殺系統(tǒng)

2020-11-11 09:49:12

計(jì)算架構(gòu)

2021-05-20 13:22:31

架構(gòu)運(yùn)維技術(shù)

2011-03-24 09:34:41

SPRING

2022-11-08 08:35:53

架構(gòu)微服務(wù)移動(dòng)

2009-08-19 04:14:00

線性鏈表

2023-02-07 10:40:30

gRPC系統(tǒng)Mac

2023-01-03 12:30:25

架構(gòu)CPUGPU

2021-04-28 08:52:22

高并發(fā)架構(gòu)設(shè)高并發(fā)系統(tǒng)

2009-07-14 16:02:42

JDBC例子

2020-11-09 06:38:00

ninja構(gòu)建方式構(gòu)建系統(tǒng)

2025-01-22 08:00:00

架構(gòu)秒殺系統(tǒng)Java

2013-03-26 14:17:21

架構(gòu)架構(gòu)設(shè)計(jì)事件驅(qū)動(dòng)

2022-04-19 08:26:20

WebAPI架構(gòu)

2011-09-08 13:41:53

Widget

2016-09-21 12:54:10

CAAS系統(tǒng)鏡像

2022-10-31 08:27:53

Database數(shù)據(jù)數(shù)據(jù)庫(kù)

2011-04-12 14:58:23

加密解密類
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)