Swift + RxSwift MVVM 模塊化項目實踐

本文主要介紹我的在 Swift 項目開發中的一些實踐經驗,供你們所借鑑或者探討。html

提升開發效率,下降 Bug 發生率,是咱們每一個開發所追隨的目標。我的認爲經過 CocoaPods 實現模塊化組件化,積累適合的組件模塊,重複利用公用模塊,不只能夠提升開發效率而且能夠有效的下降 Bug 的發生,另外能夠藉助 Gckit-CLI 等腳本工具下降重複無用的代碼編寫,進一步提升開發效率,下降低級錯誤的發生,本文如下內容主要講解我的經過 CocoaPods 結合 Gckit-CLI 實現開發效率的最大化的一些項目實踐react

項目介紹

Twilight,項目取自暮光之城電影名 全部的資源都已經開源到 Github 上了,包括服務端的接口項目git

Demo 效果演示github

App 架構設計

structurechart.png

最頂層爲 主工程,包含一些簡單的配置、路由註冊等,至關於一個空殼,模塊化以後須要注意的一點是:模塊的版本管理,每次發版必定要記錄好每一個模塊的版本號等,不然代碼回退、Bug 排查是一件很困難的事,咱們主工程中會記錄每次發版時各個模塊的版本號的。接下來就是業務層,包括各個不一樣的業務模塊,這些模塊之間的調用是經過路由實現的,不能存在引用關係的,每一個模塊會依賴一個上下文模塊項目配置模塊上下文模塊主要是管理用戶對象等用戶權限相關的事,項目配置模塊主要是總體 App 的一些配置數據、以及主題顏色和一些第三方 key 的配置等(主要爲了方便配置統一管理)。業務層是整個 App 的核心功能,而公用組件模塊是跨業務、跨 App 的,不一樣的 App 之間是能夠公用這些組件的,這一層最好做爲公司級別的供你們全部人使用。最下層爲第三方庫,通常狀況下咱們須要對第三方作一層脫離耦合的封裝,以便咱們在修改第三方時而不影響咱們的業務模塊。整個項目從上到下爲依賴關係,下層爲上層提供功能服務。正則表達式

業務模塊

模塊 介紹 地址
Carlisle 登錄註冊模塊 https://github.com/SeongBrave/Carlisle.git
Bella 上下文模塊 https://github.com/SeongBrave/Bella.git
Alice 項目配置模塊 https://github.com/SeongBrave/Alice.git
Jacob 首頁模塊 https://github.com/SeongBrave/Jacob
Twilight 主工程項目 https://github.com/SeongBrave/Twilight.git
TwilightSpecs CocoaPods 私有倉儲 https://github.com/SeongBrave/TwilightSpecs

登錄註冊模塊(Carlisle)編程

包含用戶註冊、登錄、找回密碼等功能,主要是用戶權限相關的管理界面,登錄註冊模塊是參考RxSwift官方 Demo 簡單修改完成的。swift

上下文模塊(Bella)api

上下文模塊主要用於用戶對象的管理,後期會把考慮把本地緩存等加密功能加上,上下文模塊被每一個業務模塊所依賴,用於管理用戶上下文對象,同步用戶信息的修改。緩存

項目配置模塊(Alice)bash

包括項目的主題等各個模塊的配置,涉及全部業務模塊的主題顏色配置,以及一些第三方庫的 key,各個模塊的通知等。

首頁模塊(Jacob)

商品列表模塊 取值暮光之城中 -Jacob

該模塊 90% 的代碼是經過Gckit-CLI生成的,一鍵生成包含了大部分的邏輯代碼, 上拉加載更多、下拉刷新、錯誤提示、出錯重試處理等邏輯,這些大部分的邏輯代碼是不須要修改的。

目錄結構:

├── Api
│   ├── Home_api.swift
│   └── Product_api.swift
├── Model
│   ├── Home_model.swift
│   └── Product_model.swift
├── Module
│   ├── JacobCore.swift
│   └── Jacob_router.swift
├── View
│   └── tCell
│       ├── Home_tCell.swift
│       └── Product_tCell.swift
├── ViewController
│   ├── Home_vc.swift
│   └── Product_vc.swift
└── ViewModel
    ├── Home_vm.swift
    └── Product_vm.swift
複製代碼

目錄結構分爲:

  • Api: 接口 Api
  • Model: 實例 Model
  • Module: 模塊相關管理類,包含路由註冊和提供別的模塊訪問的管理類
  • View: 相關自定義的 View
  • ViewController: 對應的 ViewController
  • ViewModel: 對應的 ViewModel
/// 界面第一次初始化
 let _ =  Observable.of(
     input.firstLoadTriger,
     reloadTrigger.withLatestFrom(input.firstLoadTriger))
     .merge().map{ Home_api.homes(page: 0, pageSize: 10)}.share(replay: 1)
     .emeRequestApiForArray(Home_model.self,activityIndicator: loading)
     .subscribe(onNext: {[unowned self] (result) in
         switch result {
         case .success(let data):
             self.hasNextPage.value = data.count == 10
             self.homeElements.value = data
             self.page = 1
         case .failure(let error):
             self.refresherror.onNext(error)
         }
     })
     .disposed(by: disposeBag)
複製代碼

上面的代碼 經過信號篩選,reloadTrigger表明點擊從新加載的事件,通過參數格式化、發送網絡請求、數據解析等數據處理,最後只需關注解析成功以後的 Model 數據而後更新 UI 界面。

公用模塊

公司的公用組件應該是長期積累的,不一樣的該功能,大部分是與業務無關的能夠擴 App 或者誇業務使用的,通過長時間的積累會慢慢完善,好比京東內部有各類各樣的模塊組件,對與新開發一個項目來講會提升不少倍,這些公用組件模塊經過 CocoaPods 管理,或者也能夠經過 Framework 管理

如下是我我的積累的一些公用庫,日常寫 Demo 啥的都是很是方便的

模塊 介紹 地址
UtilCore 基礎工具庫 https://github.com/SeongBrave/UtilCore
NetWorkCore 網絡工具庫 https://github.com/SeongBrave/NetWorkCore
EmptyDataView 列表爲空時自定義展現空界面 https://github.com/SeongBrave/EmptyDataView

RxSwift 的使用

項目中大部分的邏輯處理是藉助 RxSwift 實現的響應式編程,當界面上的每一個操做都會轉換爲一個信號而後經過對信號的各類加工網絡請求,到返回的數據 JSON 解析以及錯誤對象的處理,感受整個開發都是在開鑿水渠,等開發完了就不用管了。

網絡請求

NetWorkCore經過對Alamofire簡單封裝,配合RxSwift能夠很簡單的實現一個網絡請求,而且完成數據解析對應的 Mode 實體類,以下所示,便可實現一個用戶登陸的網絡請求。

input.loginTaps
            .withLatestFrom(Observable.combineLatest(input.username, input.password) { ($0, $1) })
            .map{Carlisle_api.login(phone: $0, password: $1)}
            .emeRequestApiForObj(User_Model.self, activityIndicator: loading)
            .subscribe(onNext: {[unowned self] (result) in
                switch result {
                case .success(let user):
                    //登錄成功就更新上下文中的登錄對象
                    Global.updateUserModel(user)
                    self.loginSuccess.onNext(user)
                case .failure(let error):
                    self.error.onNext(error)
                }
            })
            .disposed(by: disposeBag)
複製代碼

模塊路由

Swift 下一直使用URLNavigator做爲模塊之間的路由框架使用,感受很是方便

extension String {
    /// 返回路由路徑
    ///
    /// - Parameter param: 請求參數
    public func getUrlStr(param:[String:String]? = nil) -> String {
        let that = self.removingPercentEncoding ?? self
        let appScheme = Navigator.scheme
        let relUrl = "\(appScheme)://\(that)"
        guard param != nil else {
            return relUrl
        }
        var paramArr:[String] = []
        for (key , value) in param!{
            paramArr.append("\(key)=\(value)")
        }
        let rel = paramArr.joined(separator: "&")
        guard rel.count > 0 else {
            return  relUrl
        }
        return relUrl + "?\(rel)"
    }
    /// 直接經過路徑 和參數調整到 界面
    public func openURL( _ param:[String:String]? = nil) -> Bool {
        let that = self.removingPercentEncoding ?? self
        /// 爲了使html的文件通用 須要判斷是否以http或者https開頭
        guard that.hasPrefix("http") || that.hasPrefix("https") || that.hasPrefix("\(Navigator.scheme )://") else {
            var url = ""
            ///若是以 '/'開頭則須要加上本服務域名
            if that.hasPrefix("/") {
                url = UtilCore.sharedInstance.baseUrl + that
            }else{
                url = that.getUrlStr(param: param)
            }
            // 首先須要判斷跳轉的目標是不是界面仍是處理事件 若是是界面須要: push 若是是事件則須要用:open
            let isPushed = Navigator.that?.push(url) != nil
            if isPushed {
                return true
            } else {
                return (Navigator.that?.open(url)) ?? false
            }
        }
        // 首先須要判斷跳轉的目標是不是界面仍是處理事件 若是是界面須要: push 若是是事件則須要用:open
        let isPushed = Navigator.that?.push(that) != nil
        if isPushed {
            return true
        } else {
            return (Navigator.that?.open(that)) ?? false
        }
    }
}
複製代碼

這塊其實能夠更進一步的封裝,好比每次調整均可以經過正則表達式進行有效性的驗證,或者一些其餘路由規則判斷

藉助URLNavigator實現各個模塊的解耦,理論上每一個界面均可以實現互相跳轉的,在處理商品列表界面的行點擊事件(didSelectRowAt)的時候是由服務端返回的uri字段決定的,具體跳轉哪一個界面是有服務端決定的,我的的理解是界面負責產生信號,每一個信號都會通過複雜的篩選變化又會反應到界面上的,全部的跳轉事件均可以經過 URLNavigator 路由實現,好比邏輯處理、界面跳轉等事件

每一個模塊都有各自的模塊路由註冊類,好比Jacob_router.swift,包含了該模塊內部全部的可路由的界面和事件處理的路由註冊,最後會在主模塊中統一註冊

錯誤處理

監控整個 App 的全部錯誤,而後經過一些規則篩選最後展現給用戶是咱們在開發一個 App 的時候須要考慮處理的,好比在下拉列表的時候,發送網絡請求,這時候網絡請求失敗了,須要界面上展現網絡錯誤,而且顯示從新加載的按鈕,或者是若是在調用相機獲取受權的時用戶沒有受權的時候,須要提示給用戶受權相關的信息,等等這些邏輯處理均可以經過流的形式處理,在處理用戶網絡錯誤加載失敗的時候,經過 RxSwift 的一個很簡單的 Api:withLatestFrom就能實現數據從新加載,而不須要記住各類複雜的參數。

根據錯誤碼的不一樣進行不一樣的錯誤邏輯處理,以下代碼所示

/** 經過 mikerError 顯示錯誤信息 202024: 請登陸後再操做 - parameter error: */
    public func toastError(_ error:MikerError){
        if error.code == UtilCore.sharedInstance.toLoginErrorCode {
            self.toastCompletion(error.message){ _ in
                /** * 在這塊 就是跳轉到登錄模塊,若是已經跳轉就不須要直接忽略 不然 先將AppData.sharedInstance.isHasToLoginVc改成true而後再跳轉 */
                if UtilCore.sharedInstance.isHasToLoginVc == false {
                    _ = "login".openURL()
                }
            }
        } else if error.code == UtilCore.sharedInstance.toForcedupdatingErrorCode {
            /* 表示版本強制更新 */
            if UtilCore.sharedInstance.isHasForcedupdating == false {
                UtilCore.sharedInstance.isHasForcedupdating = true
                _ = "forcedupdating".openURL(["message":error.message])
            }

        } else {
            if UtilCore.sharedInstance.isDebug {
                self.toast(error.message)
            } else {
                 ///表示是生產模式
                let code = "\(error.code)"
                if code.hasPrefix("2") {
                    self.toast(error.message)
                } else {
                    self.toast(UtilCore.sharedInstance.errorMsg)
                }
            }
        }
    }
複製代碼

指令碼

與服務端確認配合肯定,經過錯誤碼路由結合能達到一種指令碼的效果,客戶端取到服務端返回的錯誤碼的時候先進行邏輯判斷,適配一些規則,若是符合則取服務端返回的uri字段,直接進行路由跳轉,不然走錯誤處理拋出。這種指令碼能夠達到一些客戶端的跳轉邏輯交由服務端來控制,好比在註冊完畢以後是跳轉首頁仍是繼續補充完詳細信息的這種需求是能夠根據服務端返回的指令碼來決定。

MVVM 架構設計

一直以爲南峯子翻譯的這兩篇文章挺不錯的雖然是 2014 的文章了,感興趣的能夠看下

另外登錄註冊模塊(Carlisle)是參考RxSwift官方 Demo 設計的,使用 MVVM 架構設計,雖然沒有嚴格遵照上面文章所說的 MVVM 引用層次,不過登錄註冊模塊(Carlisle)仍是能夠靈活的適用於不一樣的需求的在簡單修改以後。

Gckit-CLI 的使用

CocoaPods 公共組件模塊能夠很方便集成現有的模塊,可是咱們每一個業務都是徹底不同的,每一個接口返回的 JSON 文件也不同,而後咱們得手動建立與之對應的 Model,這些操做徹底沒有任何意義可是又是必須的,不過如今咱們可使用 Gckit-CLI 一鍵生成對應的全部 Model 實體類,咱們只須要把對應的 JSON 文件放到對應的目錄便可,Gckit-CLI 不只能夠生成 Model 文件,ViewModel、ViewController、View、Cell 等各類文件,而且是一鍵生成,你們能夠嘗試使用下,若是以爲能夠的話麻煩給一個Star吧 😂。

Node.js 接口服務

twilight_app 爲項目後臺的接口服務,一個客戶端開發的思惟開發的後臺接口服務 😂,功能很簡單,若是感興趣的能夠下載看下

總結

本文簡單介紹了本身在 Swift 模塊化項目中的一些實踐經驗,藉助 RxSwift 實現 MVVM 框架的設計,內容比較雜,供你們參考,隨着 Swift 5 的發佈,Swift ABI 的穩定,相信會有更多團隊會選擇 Swift 語言開發本身的 App 的, 周圍認識的不少朋友都說若是嘗試過 Swift 以後就很難再回去用 Objective-C 了,Swift 自己帶有的不少特性是 Objective-C 不具備的,呀感受又扯遠了,我我的比較喜歡經過一些工具去實現一些效率方面的提高的,經過模塊化實現代碼的複用,經過一些腳本工具實現重複無用代碼的自動生成,好比 Model 文件的生成等,這樣咱們經過藉助 CocoaPods 和 Gckit-CLI 結合使用,使咱們的開發效率大大提升了,節省出來的時間咱們專一於業務功能的開發。

🤝 最後感謝您的閱讀!

相關文章
相關標籤/搜索