架構(gòu)

設計一個App的思路:

原則:易讀 易維護 易擴展? ;技術儲備;語言選擇

組成:

1.應用入口(Appdelegate):存放推送 IM 支付回調(diào)等

2.功能模塊:根據(jù)業(yè)務進行劃分:可以靈活采用MVC MVVM MVP

3.管理模塊:登陸狀態(tài)信息 單例 網(wǎng)絡監(jiān)聽 廣告頁

4.工具類:自己寫的工具類

5.基類:一些定制化的內(nèi)容頁面 樣式 空數(shù)據(jù)頁面 無網(wǎng)絡提示頁面

6.分類:對系統(tǒng)類 自定義類增加的類別

7.宏定義文件:全局通用的宏定義 方法

8.資源文件:圖片 json xml test plist

9.第三方庫的封裝:

10.Cocoapods:


重構(gòu)需要考慮的因素:

1.明確重構(gòu)的目的和重用性

2.定義重構(gòu)完成的界限

3.持續(xù)漸進式重構(gòu)

4.確定當前的架構(gòu)狀態(tài)

5.不能忽略數(shù)據(jù)的重用性

6.管理好技術債務

7.遠離虛華的東西 追求實際

8.做好準備面對壓力,做好面對非技術的準備

9.了解當前業(yè)務

10.時刻注意代碼的質(zhì)量

11.團隊一致 做好準備


MVC:http://www.itdecent.cn/p/eedbc820d40a

MVC是軟件工程中的一種軟件架構(gòu)模式,它把軟件系統(tǒng)分為三個基本的部分:模型Model、視圖View以及控制器Controller;

數(shù)據(jù)Model: 負責封裝數(shù)據(jù)、存儲和處理數(shù)據(jù)運算等工作

視圖View: 負責數(shù)據(jù)展示、監(jiān)聽用戶觸摸等工作

控制器Controller: 負責業(yè)務邏輯、事件響應、數(shù)據(jù)加工等工作

在iOS中,M和V之間禁止通信,必須由C控制器層來協(xié)調(diào)M和V之間的變化,C對M和V的訪問是不受限的

Controller 可以直接與 Model 對話(讀寫調(diào)用 Model),Model 通過 Notification 和 KVO 機制與 Controller 間接通信

Controller 可以直接與 View 對話,通過 outlet,直接操作 View,outlet 直接對應到 View 中的控件,View 通過 action 向 Controller 報告事件的發(fā)生(如用戶 Touch 我了)。Controller 是 View 的直接數(shù)據(jù)源(數(shù)據(jù)很可能是 Controller 從 Model 中取得并經(jīng)過加工了)。Controller 是 View 的代理(delegate),以同步 View 與 Controller

MVC的缺點在于并沒有區(qū)分業(yè)務邏輯和業(yè)務展示, 這對單元測試很不友好


MVP:

MVP模式是MVC模式的一個演化版本(好像所有的模式都是出自于MVC~~),MVP全稱Model-View-Presenter;

MVP的 V 層是由UIViewController 和UIView 共同組成;

Model:與MVC中的model沒有太大的區(qū)別。主要提供數(shù)據(jù)的存儲功能,一般都是用來封裝網(wǎng)絡獲取的json數(shù)據(jù)的集合

Presenter:作為model和view的中間人,從model層獲取數(shù)據(jù)之后傳給view,使得View和model沒有耦合

view 將委托presenter 對它自己的操作,(簡單來說就是presenter發(fā)命令來控制view的交互,要你隱藏就隱藏,叫你show 你就乖乖的show)

presenter擁有對 view交互的邏輯(就是上面說的意思)

presenter跟model層通信,"Present"一方面通過Service層調(diào)用接口獲取數(shù)據(jù)給Model層,并將數(shù)據(jù)轉(zhuǎn)化成對適應UI的數(shù)據(jù)并更新view

presenter不需要依賴UIKit

view層是單一,因為它是被動接受命令,沒有主動能力

presenter 作為業(yè)務邏輯的處理者,首先要向Service層拿數(shù)據(jù)賦值給model,所以它將可以向model層通信。其次,UI的處理權(quán)移交給了它,所以它需要與view成通訊,發(fā)送命令更新UI。同時,UI的響應將觸發(fā)業(yè)務邏輯的處理,所以view 層向presenter層通訊,告訴他用戶做了什么操作,需要你反饋對應的數(shù)據(jù)來更新UI。這樣就完成了從用戶交互獲得交互反饋到整個業(yè)務邏輯。

關于C端和P端的循環(huán)引用的問題, 直接用weak關鍵字就可以解決了

總得來說MVP的好處就是解除view與model的耦合,使得view或model有更強的復用性

只需要初始化P層, 然后調(diào)P層的接口就可以了. 至于P層內(nèi)部的邏輯, 我不需要知道

V層也只專注于視圖的創(chuàng)建

M層只專注于模型的構(gòu)建(字典->模型)

優(yōu)點:

對Controller進行瘦身,View和Model之間不存在耦合,同時也將業(yè)務邏輯從View中抽離,復用性更好

缺點:

由于對視圖的渲染放在了Presenter中,所以視圖和Presenter的交互會過于頻繁。還有一點需要明白,如果Presenter過多地渲染了視圖,往往會使得它與特定的視圖的聯(lián)系過于緊密。一旦視圖需要變更,那么Presenter也需要變更了


MVVM:

MVVM

在MVVM 中,view 和 view controller正式聯(lián)系在一起,我們把它們視為一個組件

view 和 view controller 都不能直接引用model,而是引用視圖模型(viewModel)

viewModel 是一個放置用戶輸入驗證邏輯,視圖顯示邏輯,發(fā)起網(wǎng)絡請求和其他代碼的地方

使用MVVM會輕微的增加代碼量,但總體上減少了代碼的復雜性

MVVM模式將Presenter改名為ViewModel,基本上與MVP模式完全一致。

唯一的區(qū)別是,它采用雙向綁定(data-binding)?: View<->ViewModel, ViewModel作為Model中值的映射,是數(shù)據(jù)發(fā)生改變時,通知View中發(fā)生改變,以后不需要考慮View和Model之間的交互更新,只需著手界面布局邏輯即可。

①View和Model 不直接關聯(lián),而是通過ViewModel作為樞紐,溝通View和Model之間的關系。

②View中控件的值與屬性進行綁定,通過KVO鍵值觀察(這樣當model的值發(fā)生變化時,View會自動發(fā)生改變)?

View和Model通過ViewModel實現(xiàn)動態(tài)關聯(lián)

MVVM 的注意事項

view 引用viewModel ,但反過來不行(即不要在viewModel中引入#import UIKit.h,任何視圖本身的引用都不應該放在viewModel中)(PS:基本要求,必須滿足)

viewModel 引用model,但反過來不行

MVVM 的優(yōu)勢:

低耦合:View 可以獨立于Model變化和修改,一個 viewModel 可以綁定到不同的 View 上

可重用性:可以把一些視圖邏輯放在一個 viewModel里面,讓很多 view 重用這段視圖邏輯

獨立開發(fā):開發(fā)人員可以專注于業(yè)務邏輯和數(shù)據(jù)的開發(fā) viewModel,設計人員可以專注于頁面設計

可測試:通常界面是比較難于測試的,而 MVVM 模式可以針對 viewModel來進行測試

MVVM 的弊端:

數(shù)據(jù)綁定使得Bug 很難被調(diào)試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問題。數(shù)據(jù)綁定使得一個位置的 Bug 被快速傳遞到別的位置,要定位原始出問題的地方就變得不那么容易了

對于過大的項目,數(shù)據(jù)綁定和數(shù)據(jù)轉(zhuǎn)化需要花費更多的內(nèi)存(成本)

綁定是一種響應式的通信方式。當被綁定對象某個值的變化時,綁定對象會自動感知,無需被綁定對象主動通知綁定對象??梢允褂肒VO和RAC實現(xiàn)。例如在Label中顯示倒計時,是V綁定了包含定時器的VM。


組件化:(使用cocoapods進行組件化的實現(xiàn))

組件化方案的幾種實現(xiàn):

方案一:url-block

通過在啟動時注冊組件提供的服務,把調(diào)用組件使用的url和組件提供的服務block對應起來,保存到內(nèi)存中。在使用組件的服務時,通過url找到對應的block,然后獲取服務

出現(xiàn)的問題:

1、需要在內(nèi)存中維護url-block的表,組件多了可能會有內(nèi)存問題

2、url的參數(shù)傳遞受到限制,只能傳遞常規(guī)的字符串參數(shù),無法傳遞非常規(guī)參數(shù),如UIImage、NSData等類型

3、沒有區(qū)分本地調(diào)用和遠程調(diào)用的情況,尤其是遠程調(diào)用,會因為url參數(shù)受限,導致一些功能受限

4、組件本身依賴了中間件,且分散注冊使的耦合較多

方案二:protocol-class

通過protocol定義服務接口,組件通過實現(xiàn)該接口來提供接口定義的服務,具體實現(xiàn)就是把protocol和class做一個映射,同時在內(nèi)存中保存一張映射表,使用的時候,就通過protocol找到對應的class來獲取需要的服務

出現(xiàn)的問題:

依然沒有解決組件依賴中間件的問題、內(nèi)存中維護映射表的問題、組件的分散調(diào)用的問題

方案三:target-action

通過給組件包裝一層wrapper來給外界提供服務,然后調(diào)用者通過依賴中間件來使用服務;其中,中間件是通過runtime來調(diào)用組件的服務,是真正意義上的解耦,也是該方案最核心的地方。具體實施過程是給組件封裝一層target對象來對外提供服務,不會對原來組件造成入侵;然后,通過實現(xiàn)中間件的category來提供服務給調(diào)用者,這樣使用者只需要依賴中間件,而組件則不需要依賴中間件

方案四:使用cocoapods進行組件化的實現(xiàn))

具體就是建立一個項目工程的私有化倉庫,然后把各個組件的podspec上傳到私有倉庫,在需要用到組件時,直接從倉庫里面取


1.添加Podfile文件?pod init?然后會發(fā)現(xiàn)你的工程目錄下多了Podfile文件

2.生成xcworkspace工程?pod install

3.新建一個Lib(自己起名)文件夾,用來存放組件庫(其他獨立工程)然后cd到Lib下 執(zhí)行?pod lib create?

XXX(工程名)

4.打開新建的XXX(工程名)工程里的Example,可以看到pods里面,有個ReplaceMe的文件,意思就是要替換它,換成我們自己需要對外提供的類

5.新建一個類,比如TRUXXX,復制粘貼到ReplaceMe同級目錄下,并刪掉ReplaceMe.m文件

6.?之后cd到Lib/TRUXXX/Example/文件目錄下,執(zhí)行pod install?這個時候在Development Pods文件下會多出這兩個文件,這就是本地開發(fā)的pods文件

7.而Podfile的內(nèi)容其實是

pod 'TRUXXX', :path => '../'

說明他獲取的是本地路徑

然后刪除Example for TRUXXX里面的TRUXXX類,不然運行會因為類重復報錯。

至此,一個組件的本地庫就創(chuàng)建完成了。

8. 殼工程使用本地組件庫

首先cd到殼工程LZDemo目錄下,修改LZDemo的Podfile文件,增加

pod 'TRUXXX', :path => 'Lib/TRUXXX'

執(zhí)行 pod install


組件需要對外提供依賴關系。所以我們還得多做一步操作,那就是增加podspec文件

以TRUXXX為例,cd到TRUXXX目錄下,執(zhí)行

git tag 0.1.0

git push --tags

這個tag分支就是將來提供給別人依賴的版本號分支,有了它,別人使用你的組件的時候就可以根據(jù)版本號來控制了。

改好后,在上傳之前,最好先本地檢查一下podspec是否合法

執(zhí)行下面語句

pod lib lint --verbose

如果出現(xiàn)passed validation,說明通過,可以提交到cocoapods上了

成功后,就可以pod search到我們提交的庫了

ps:如果搜不到,不是沒傳成功,是我們的本地搜索庫沒更新,可以先刪除~/Library/Caches/CocoaPods目錄下的search_index.json文件或者pod repo update一下

rm~/Library/Caches/CocoaPods/search_index.json


組件間通訊

1. Protocol注冊方案

通過JJProtocolManager 作為中間轉(zhuǎn)化

+ (void)registerModuleProvider:(id)provider forProtocol:(Protocol*)protocol;

+ (id)moduleProviderForProtocol:(Protocol *)protocol;

有組件對外提供的procotol和組件提供的服務由中間件統(tǒng)一管理,每個組件提供的procotol和服務是一一對應的。

例如:

在JJLoginProvider中:load方法會應用啟動的時候調(diào)用,就會在JJProtocolManager進行注冊。JJLoginProvider遵守了JJLoginProvider協(xié)議,這樣就可以對外根據(jù)業(yè)務需求提供一些方法。

+ (void)load

{

? ? [JJProtocolManager registerModuleProvider:[self new] forProtocol:@protocol(JJLoginProtocol)];

}

- (UIViewController *)viewControllerWithInfo:(id)userInfo needNew:(BOOL)needNew callback:(JJModuleCallbackBlock)callback{

? ? CLoginViewController *vc = [[CLoginViewController alloc] init];

? ? vc.jj_moduleCallbackBlock = callback;

? ? vc.jj_moduleUserInfo = userInfo;

? ? return vc;

}

這樣就可以在需要登錄業(yè)務模塊的地方,通過JJProtocolManager取出JJLoginProtocol對應的服務提供者JJLoginProvider,直接獲取。如下:

id<jjwebviewvcmoduleprotocol>?provider?=?[JJProtocolManager?moduleProviderForProtocol:@protocol(JJWebviewVCModuleProtocol)];?

???UIViewController?*vc?=[provider?viewControllerWithInfo:obj?needNew:YES?callback:^(id?info)?{?

???????if?(callback)?{?

???????????callback(info);?

???????}?

???}];?

???vc.hidesBottomBarWhenPushed?=?YES;?

???[self.currentNav?pushViewController:vc?animated:YES];</jjwebviewvcmoduleprotocol>?

2. URL注冊方案?OPENURL

原理:

通過url注冊服務, 其他地方通過url, 獲取服務;框架在維護一個url-block的表格

特點:

每個業(yè)務組件, 都需要依賴這個框架

url維護成本高 硬解碼

可以在組件內(nèi)部任何地方調(diào)用/注冊服務, 沒有必要統(tǒng)一組件接口服務

[MGJRouter?registerURLPattern:@"mgj://detail?id=:id"?toHandler:^(NSDictionary?*routerParameters)?{?

NSNumber?*id?=?routerParameters[@"id"];?

//create?view?controller?with?id?

//?pushview?controller?

}];?

首頁只需調(diào)用 [MGJRouter openURL:@"mgj://detail?id=404"] 就可以打開相應的詳情頁。


3. Target-Action runtime調(diào)用方案

原理:

每個組件, 提供一個統(tǒng)一披露的接口文件

額外的維護一個中間件的分類擴展(在此處進行硬解碼 通過運行時進行物理解耦)

其他地方通過target-action;的方案進行交互

特點:

統(tǒng)一了組件api服務

組件與框架之間無依賴關系

需要額外維護中間件類擴展

實現(xiàn):

我們主要是依賴CTMediator?這個中間件工具類中主要使用如下方法:

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget

方法內(nèi)部使用Runtime調(diào)用 需要傳三個參數(shù)

當前需要調(diào)用的類名 (字符串)

當前需要調(diào)用類的方法名 (字符串)

需要傳的參數(shù) (字典形式)

# 通過Runtime 把字符串 轉(zhuǎn)換類

Class targetClass = NSClassFromString(ClassString);

id? target = [[targetClass alloc] init];

# 把字符串轉(zhuǎn)換成事件

SEL action = NSSelectorFromString(actionString);

# 如果當前類中有這個事件 那就執(zhí)行這個事件 把需要的參數(shù)傳值

if ([target respondsToSelector:action]) {

? ? return [target performSelector:action withObject:params];

}

4.使用cocoapods進行組件化的實現(xiàn))

1.每個業(yè)務組件庫里面會有一個控制器的配置文件(路由配置文件),標記著每個控制器的key;

2.在App每次啟動時,組件通訊的工具類里面需要解析控制器配置文件(路由配置文件),將其加載進內(nèi)存;

3. 在內(nèi)存中查詢路由配置,找到具體的控制器并動態(tài)生成類,然后使用==消息發(fā)送機制==進行調(diào)用函數(shù)、傳參數(shù)、回調(diào),都能做到。

5. 依賴注入

組件化的好處:

業(yè)務分層、解耦,使代碼變得可維護;

有效的拆分、組織日益龐大的工程代碼,使工程目錄變得可維護;

便于各業(yè)務功能拆分、抽離,實現(xiàn)真正的功能復用;

業(yè)務隔離,跨團隊開發(fā)代碼控制和版本風險控制的實現(xiàn);

模塊化對代碼的封裝性、合理性都有一定的要求,提升開發(fā)同學的設計能力;

在維護好各級組件的情況下,隨意組合滿足不同客戶需求;

http://www.itdecent.cn/p/59c2d2c4b737創(chuàng)建組件化的步驟

各個組件該如何進行拆分:

1.??項目主工程:主工程就是一個空殼子工程

2.??業(yè)務組件:業(yè)務組件就是各個獨立的產(chǎn)品業(yè)務功能模塊

3.??基礎工具類組件:基礎工具類是各個互相獨立,沒有任何依賴的工具組件。它們和其它的工具組件、業(yè)務組件等沒有任何依賴關系。這類組件例如有:對數(shù)組,字典進行異常保護的Safe組件,對數(shù)組功能進行擴展Array組件,對字符串進行加密處理的加密組件等等。

4.? 中間件組件:中間調(diào)度者就是一個功能獨立的中間件組件

5.??基礎UI組件:視圖組件就比較常見了,例如我們封裝的導航欄組件,Modal彈框組件,PickerView組件等。

6.??業(yè)務工具組件:這類組件是為各個業(yè)務組件提供基礎功能的組件。這類組件可能會依賴到其他的組件。例如:網(wǎng)絡請求組件,圖片緩存組件,jspatch組件等等

詳細操作步驟:

第一步:

我們先創(chuàng)建一個空的iOS工程項目:MainProject,這個空項目作為我們的主工程項目,就是上面所說的殼子工程項目,然后初始化pod

第二步:

我們創(chuàng)建一個空工程項目:ModuleA,這個ModuleA 項目作為我們的業(yè)務A組件。然后我們初始化pod,初始化podspec文件

第三步:

我們創(chuàng)建一個空工程項目:ModuleB,這個ModuleB 項目作為我們的業(yè)務B組件。然后我們初始化pod,初始化podspec文件

第四步:

我們創(chuàng)建一個空工程項目:ComponentMiddleware,這個項目就是我們上面所說的中間調(diào)度者。然后我們初始化pod,初始化podspec文件。

第五步:

我們創(chuàng)建一個空工程項目: ModuleACategory,這個工程是對應業(yè)務組件A的一個分類工程。然后我們初始化pod,初始化podspec文件。

第六步:

我們創(chuàng)建一個空工程項目: ModuleBCategory,這個工程是對應業(yè)務組件B的一個分類工程。然后我們初始化pod,初始化podspec文件。

第七步:

我們在主工程MainProject的Podfile中引入我們的業(yè)務組件B工程ModuleB,以及引入我們的ModuleB的分類工程:ModuleBCategory。然后我們pod install。這時已將這兩個組件庫引入到我們的主工程中了。

#import <ModuleBCategory/ComponentScheduler+ModuleB.h>

- (void)moduleB {

? ? UIViewController *VC = [[ComponentScheduler sharedInstance] ModuleB_viewControllerWithCallback:^(NSString *result) {

? ? ? ? NSLog(@"resultB: --- %@", result);

? ? }];

? ? [self.navigationController pushViewController:VC animated:YES];

}

第八步:

上面第七步中,我們用到了ModuleBCategory 這個分類工程。這個工程我們只對外暴露了兩個文件。這兩文件是上面的中間調(diào)度者的分類,也就是說是中間件的分類。我們先來看下這個分類文件的.h 和.m 實現(xiàn)

#import "ComponentScheduler+ModuleB.h"

@implementation ComponentScheduler (ModuleB)

- (UIViewController *)ModuleB_viewControllerWithCallback:(void(^)(NSString *result))callback {

? ? NSMutableDictionary *params = [[NSMutableDictionary alloc] init];

? ? params[@"callback"] = callback;

? ? return [self performTarget:@"ModuleB" action:@"viewController" params:params shouldCacheTarget:NO];

}

@end

第九步:

這個分類的作用你可以理解為我們提前約定好Target的名字和Action的名字,因為這兩個名字中間件組件中會用到。

因為上面第八步中引用到中間件工程,這里我們就來看下中間件工程到底做了什么工作。還記得上面第八步中,我們調(diào)用了一個中間件提供的函數(shù):performTarget:action:params:shouldCacheTarget吧,這個是中間件核心函數(shù)。

這個函數(shù)最終調(diào)用到蘋果官方提供的函數(shù):[target performSelector:action withObject:params];

看到?performSelector: withObject:?大家應該就比較熟悉了,iOS的消息傳遞機制。

[Target_ModuleB performSelector:Action_viewController withObject:params];

上面這行偽代碼意思是: Target_ModuleB這個類 調(diào)用它的 Action_viewController: 方法,然后傳遞的參數(shù)為 params。

細心的小伙伴們就會發(fā)現(xiàn),我們沒有看到過哪里有這個Target_ModuleB 類啊,更沒有看到Target_ModuleB 調(diào)用它的 Action_viewController: 方法啊。

是的,這個Target_ModuleB類和類的Action_viewController方法就在第十步中講解到。

第十步:

業(yè)務組件B除了提供組件B的業(yè)務功能外,業(yè)務組件B還需要為我們提供一個Target文件

#import "Target_ModuleB.h"

#import "ModuleBViewController.h"

@implementation Target_ModuleB

- (UIViewController *)Action_viewController:(NSDictionary *)params {

? ? ModuleBViewController *VC = [[ModuleBViewController alloc] init];

? ? return VC;

}

@end

從上面的實現(xiàn)文件中,我們可以看到,Target文件的作用也很簡單,就是為我們提供導航跳轉(zhuǎn)的目標控制器實例對象。這里的目標控制器實例就是業(yè)務組件B的ModuleBViewController?實例。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容