細說IOS工程架構(持續更新)

流河_旱樹發表於2020-11-19

一、框架模式的選擇

1. MVC框架

MVC全名是Model View Controller,是模型(model)-檢視(view)-控制器(controller)的縮寫,一種軟體設計典範,用一種業務邏輯、資料、介面顯示分離的方法組織程式碼,將業務邏輯聚集到一個部件裡面,在改進和個性化定製介面及使用者互動的同時,不需要重新編寫業務邏輯。MVC被獨特的發展起來用於對映傳統的輸入、處理和輸出功能在一個邏輯的圖形化使用者介面的結構中。

1.1 MVC 程式設計模式

MVC 是一種使用 MVC(Model View Controller 模型-檢視-控制器)設計應用程式的模式

  • 檢視(View):負責資料展示、監聽使用者觸控等工作

  • 控制器(Controller):負責業務邏輯、事件響應、資料加工等工作

  • 模型(Controller):負責封裝資料、儲存和處理資料運算等工作

1.2 MVC通訊特點

1. Model和View永遠不能相互通訊,只能通過Controller傳遞。

2. Controller可以直接與Model通訊(讀寫呼叫Model),Model通過Notification和KVO機制與Controller間接通訊。

3. Controller與View通過Target/Action,delegate和datasource三種模式進行通訊。通過這三種模式,View就可以向Controller通訊,Action/Target 模式來讓Controller 監聽View 觸發的事件。View又通過Data source和delegate進行資料獲取和某些通訊操作。

1.3 MVC優缺點

1.易用性:與其他幾種模式相比最小的程式碼量,維護起來也較為容易。 

2. 可測性:由於糟糕的分散性,只能對Model進行測試。

3. 均衡性:厚重的ViewController、無處安放的網路邏輯與資料邏輯。

2. MVCS

蘋果自身就採用的是這種架構思路,從名字也能看出,也是基於MVC衍生出來的一套架構。從概念上來說,它拆分的部分是Model部分,拆出來一個Store。這個Store專門負責資料存取。

從實際操作的角度上講,它拆開的是Controller。因為Controller做了資料儲存的事情,就會變得非常龐大,那麼就把Controller專門負責存取資料的那部分抽離出來,交給另一個物件去做,這個物件就是Store。這麼調整之後,整個結構也就變成了真正意義上的MVCS。

  • 檢視(View):使用者介面

  • 控制器(Controller):業務邏輯及處理

  • 模型(Model):資料儲存

  • 儲存器(Store):資料處理邏輯

MVCS是基於瘦Model的一種架構思路,把原本Model要做的很多事情中的其中一部分關於資料儲存的程式碼抽象成了Store,在一定程度上降低了Controller的壓力。

3. MVP模式

MVP(Model-View-Presenter)是從經典的模式MVC演變而來,它們的基本思想有相通的地方Controller/Presenter負責邏輯的處理,Model提供資料,View負責顯示。

3.1 MVP模式的優缺點

1. 模型與檢視完全分離,我們可以修改檢視而不影響模型。

2. 可以更高效地使用模型,因為所有的互動都發生在Presenter內部。

3. 可以將一個Presenter用於多個檢視,而不需要改變Presenter的邏輯。

4. 如果我們把邏輯放在Presenter中,那麼我們就可以脫離使用者介面來測試這些邏輯。

5. 由於對檢視的渲染放在了Presenter中,檢視和Presenter的互動會過於頻繁,一旦檢視需要變更,Presenter也需要變更了。

3.2 MVP與MVC區別:

1. 在MVP中View並不直接使用Model,它們之間的通訊是通過Presenter (MVC中的Controller)來進行的,所有的互動都發生在Presenter內部,而在MVC中View會直接從Model中讀取資料而不是通過 Controller。

2. 在MVC裡,View是可以直接訪問Model的。View裡會包含Model資訊,不可避免的還要包括一些業務邏輯。 在MVC模型裡,Model不依賴於View,但是View是依賴於Model的。因為有一些業務邏輯在View裡實現了,導致View的可重用性降低。

3. 在MVC裡,不建議在 View 中依賴 Model,而是儘可能把業務邏輯都放在 Controller 中處理,使View 只和 Controller 互動。

3.MVVM框架

MVVM是Model-View-ViewModel的簡寫。它本質上就是MVC 的改進版。MVVM 就是將其中的View 的狀態和行為抽象化,讓我們將檢視 UI 和業務邏輯分開。當然這些事 ViewModel 已經幫我們做了,它可以取出 Model 的資料同時幫忙處理 View 中由於需要展示內容而涉及的業務邏輯。

MVVM(Model-View-ViewModel)框架的由來便是MVP(Model-View-Presenter)模式與WPF結合的應用方式時發展演變過來的一種新型架構框架。它立足於原有MVP框架並且把WPF的新特性糅合進去,以應對客戶日益複雜的需求變化。

3.1 MVVM模式的組成部分

  • 模型模型是指代表真實狀態內容的領域模型(物件導向),或指代表內容的資料訪問層(以資料為中心)。

  • 檢視:就像在MVC和MVP模式中一樣,檢視是使用者在螢幕上看到的結構、佈局和外觀(UI)。

  • 檢視模型檢視模型是暴露公共屬性和命令的檢視的抽象。在檢視模型中,繫結器在檢視和資料繫結器之間進行通訊。

3.2 MVVM優點

  • 低耦合:View可以獨立於Model變化和修改,一個ViewModel可以繫結到不同的View上,當View變化的時候Model可以不變,當Model變化的時候View也可以不變。

  • 可重用性:你可以把一些檢視邏輯放在一個ViewModel裡面,讓很多view重用這段檢視邏輯。

  • 獨立開發:開發人員可以專注於業務邏輯和資料的開發(ViewModel),設計人員可以專注於頁面設計。

  • 可測試:介面素來是比較難於測試的,而現在測試可以針對ViewModel來寫。

3.2 MVVM與MVP區別:

mvvm模式將Presener改名為View Model,基本上與MVP模式完全一致,唯一的區別是,MVVM可以結合RAC(MVVM+RAC)實現view與ViewModel的雙向繫結,這樣開發者就不用處理接收事件和View更新的工作。

二、基礎類庫與cocoaPod應用

1. 基礎類庫的目錄分佈

BaseKit-專案的基礎程式碼目錄,存放著通用的基礎類庫,應用於各個業務模組。

ServiceMgr:服務公共類庫,與Kernal相比偏業務性,如資料上報、行為驗證、使用者資訊、共享資料等。

Kernal:基礎公共類庫,主要提供基礎能力,如Network、Log、dataBase等。

Utils:工具類集合(類方法),如Utils、JPUtils、Authoritys、Encrypt等 。

Categorys:公共類別擴充套件,可按照類屬性的不同分為不同的子目錄。

Views:通用檢視元件,如ZoomScrollView、BannerView、AlertView等。

Base:基礎類,又分為MVC三個子目錄,封裝存放Basic類。

2. cocoaPod應用

cocoaPod應用IOS開發者應該都比較熟悉,主要是用關聯第三方庫與私有元件庫,方便版本迭代管理。

2.1 GitHub第三方庫

CocoaPods詳解之-使用篇:https://blog.csdn.net/meegomeego/article/details/24005567

2.2 GitLab私有庫

CocoaPod-spec私有庫配置:https://blog.csdn.net/z119901214/article/details/90241251

三、業務元件&元件通訊

1. 業務元件

業務元件主要是指作為一個大的業務模組,單獨分離成一個元件的形式,如電商模組、聊天模組、部落格等。業務元件之間不存在耦合程式碼,由元件通訊中間層實現彼此的通訊。

1.1 組價分層方式

  • 目錄分層:在主業務目錄下,按照不同的業務元件建立不同的目錄,業務模組獨立,元件之間不直接呼叫API。

  • 多project分層:通過xcworkspace的方式,不同的業務模組建立各自的project工程,業務project工程run成功後,將framework引入主工程中。

1.2 元件分層方式的特點

  • 目錄分層:目錄分層比較簡單,沒有實現真正的程式碼分割,所以目錄與目錄直接的類是可以引用的,這樣就很依賴於團隊開發的規範性。

  • 多project分層:多project分層實現了不同業務元件程式碼的分割,作為framework的形式引入,靜態framework的形式引入更新迭代成本比較高,動態framework的形式引入又會影響到編譯打包的效率,而且不利於cocoaPod的使用。

2. 元件之間的通訊

2.1 UR-Block(MGJRouter)

/*********** 業務元件中註冊 ***********/
[[MKRouter sharedInstance] registerHandler:^(MKRouteRequest *request) {
// 跳轉至商祥頁
// request.callBack(nil, @{@"productId" : request[@"productId"]});
} forRoute:MKString(@".*product/detail.*\\?(.*)\\&(.*)$")];


/*********** 模組呼叫 ***********/
[[MKRouter sharedInstance] handleURL:[NSURL URLWithString:@"weixin://com.apple.iphone/product/detail?productId=ID985632"] params:nil targetCallBack:^(NSError *error, NSDictionary *responseObject) {

}];

資料埋點: "appData/click?params={eventId:0925D6A034A58F06}"

原生商詳跳轉: "product/detail?productId=id1314785643"

weex商詳跳轉: "weex/page?params='jsonString的URL編碼'"

web商詳跳轉: "web/page?params='jsonString的URL編碼'"

flutter商詳跳轉: "flutter/page?params='jsonString的URL編碼'"

2.2 Target-Action(CTMediator)

/*********** 公共實現 ***********/
#import "MKMediator+Feature.h"

static NSString * const kTargeFeature = @"Feature";

static NSString * const kActionViewControllerForFeature = @"viewControllerForFeature";

@implementation MKMediator (Feature)

- (UIViewController *)mediator_ViewControllerForFeature:(MKFeatureActionModel *)params {
    return  [self call:kTargeFeature action:kActionViewControllerForFeature parameters:params];
}

@end

/*********** 業務元件中實現 ***********/
#import "MKMediator+Feature.h"
#import "MKTarget_Feature.h"
#import "AudioVideoPraticeVC.h"

@implementation MKTarget_Feature

- (UIViewController *)action_viewControllerForFeature:(MKActionModel *)params {
    AudioVideoPraticeVC* audioVC = [[AudioVideoPraticeVC alloc] init];
    return audioVC;
}

@end

/*********** 模組呼叫 ***********/
MKActionModel* acitonModel = [[MKActionModel alloc] init];
actionModel.keyValues = @{};
actionModel.complete = ^(id result) {

};
UIViewController* audioVC = [[MKMediator shared] mediator_ViewControllerForFeature:acitonModel];

原生->原生錄音頁:直接呼叫對應業務元件的公共API

外部->原生錄音頁 "native/page?target=Feature&action=viewControllerForFeature&params='jsonString的URL編碼'"(直接解析url呼叫)

外部->原生錄音頁 "native/page/audio/index.html?key=value&key1=value1"(通過url正則匹配到頁面的相關配置,最終得到target、action、params)

開啟hybrid容器頁:通過url正則匹配到頁面的相關配置,再呼叫業務容器元件的公共API實現頁面的跳轉和渲染

2.3 Protocol-Class

面向介面程式設計,通過protocol定義協議介面與屬性(注意:protocol的屬性只是一個宣告,並沒有實際用途,需要實現協議的類本身定義了該屬性)。

/* 定義協議方法與屬性 */
@protocol MKOpenURLProtocol <NSObject>

@property (nonatomic, assign) BOOL isPresent;

- (BOOL)openURLWithURLString:(NSString *)URLString params:(NSDictionary *)params;

@end

 MKOpenURLExecutor宣告瞭MKOpenURLProtocol,並實現其協議方法。

/* 執行者-MKOpenURLExecutor.h */
@interface MKOpenURLExecutor : NSObject <MKOpenURLProtocol>

@property (nonatomic, assign) BOOL isPresent;

@end

/* 實現者-MKOpenURLExecutor.m */
- (BOOL)openURLWithURLString:(NSString *)URLString params:(NSDictionary *)params {
    // do something
    return YES;
}

@end

上面講的這種宣告協議再到類的實現,雖然是應用了介面程式設計的方式,但是業務元件之間還是會有直接呼叫的關係,所以需要有個中間者實現protocol與介面物件的一一匹配。

/* 中間者-MKProtocolManager.h */
@interface MKProtocolManager : NSObject

+ (instancetype)sharedInstance;

- (void)setObject:(id)object protocol:(Protocol *)protocol;

- (id)objectWithProtocol:(Protocol *)protocol;

@end

/* 中間者-MKProtocolManager.m */
@interface MKProtocolManager ()

@property (nonatomic, strong) NSMutableDictionary* protocolSet;

@end

@implementation MKProtocolManager

+ (instancetype)sharedInstance {
    static MKProtocolManager* instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
        instance.protocolSet = [NSMutableDictionary dictionary];
    });
    return instance;
}

- (void)setObject:(id)object protocol:(Protocol *)protocol {
    if (object && protocol) {
        [_protocolSet setObject:object forKey:NSStringFromProtocol(protocol)];
    }
}

- (id)objectWithProtocol:(Protocol *)protocol {
    if (protocol) {
      return [_protocolSet objectForKey:NSStringFromProtocol(protocol)];
    }
    return nil;
}

@end

業務元件A的協議與介面物件的繫結,以及其他業務元件中呼叫業務元件A的協議介面。

/* 業務元件A的協議與介面物件的一一匹配 */
+ (void)registerProtocol {
    MKOpenURLExecutor* executor = [[MKOpenURLExecutor alloc] init];
    executor.isPresent = NO; // 內部屬性配置
    [[MKProtocolManager sharedInstance] setObject:executor protocol:@protocol(MKOpenURLProtocol)];
}

/* 其他業務元件中呼叫業務元件A的協議介面 */
- (void)openURL {
    id <MKOpenURLProtocol> protocol = [[MKProtocolManager sharedInstance] objectWithProtocol:@protocol(MKOpenURLProtocol)];
    [protocol openURLWithURLString:@"" params:@{}];
}

2.4 三種通訊方式的特點

  • URL-Block:能解決元件間的依賴,路由配置靈活,需要去註冊&維護路由表,方法呼叫不夠直觀。

  • Target-Action:統一了元件api服務,元件與框架之間無依賴關係,需要額外維護中介軟體類擴充套件,方法呼叫不夠直觀。

  • Protocol-Class:面向介面程式設計,方法呼叫比較直觀,每個元件都需要宣告一個對外的協議,和一個用於實現的類。

3. 頁面路由規則配置(推送、scheme、JSAPI-openURL)

type(型別)key(頁面key)url(頁面url)regular(正則)arguments(頁面配置)
nativehome_pargeurlregulararguments
weborder_detailurlregulararguments
weexstar_infourlregularlx_url、arguments
react nativeorder_listurlregularrn_url、arguments

flutter

mine_detailurlregularrouteName、arguments

通過url正則匹配到相關的路由配置,生成對應型別的VC容器,從而實現頁面跳轉。(url連結必須實現URL編解碼)

native:通過頁面配置資訊與url引數,生成對應的原生頁面,最終實現頁面的跳轉與渲染。

web:直接通過url,生成WebVC,實現頁面跳轉和H5資源載入。

weex:獲取weex資原始檔的下載連結及頁面配置引數,傳遞給weex容器實現資源載入和頁面渲染。

react naive:獲取rn資原始檔的下載連結及頁面配置引數,傳遞給rn容器實現資源載入和頁面渲染。

flutter:配置相關的routeName及頁面配置引數,傳遞給Flutter容器實現flutter端頁面的渲染。

相關文章