本文主要介紹個人在 Swift 項目開發(fā)中的一些實踐經(jīng)驗导街,供大家所借鑒或者探討耻蛇。
提高開發(fā)效率吭从,降低 Bug 發(fā)生率朝蜘,是我們每個開發(fā)所追隨的目標。個人認為通過 CocoaPods 實現(xiàn)模塊化組件化涩金,積累適合的組件模塊芹务,重復利用公用模塊,不僅可以提高開發(fā)效率并且可以有效的降低 Bug 的發(fā)生鸭廷,另外可以借助 Gckit-CLI 等腳本工具降低重復無用的代碼編寫,進一步提高開發(fā)效率熔吗,降低低級錯誤的發(fā)生辆床,本文以下內(nèi)容主要講解個人通過 CocoaPods 結合 Gckit-CLI 實現(xiàn)開發(fā)效率的最大化的一些項目實踐
項目介紹
Twilight,項目取自暮光之城電影名
所有的資源都已經(jīng)開源到 Github 上了桅狠,包括服務端的接口項目
Demo 效果演示
App 架構設計
最頂層為 主工程
讼载,包含一些簡單的配置、路由注冊等中跌,相當于一個空殼咨堤,模塊化之后需要注意的一點是:模塊的版本管理,每次發(fā)版一定要記錄好每個模塊的版本號等漩符,否則代碼回退一喘、Bug 排查是一件很困難的事,我們主工程中會記錄每次發(fā)版時各個模塊的版本號的。接下來就是業(yè)務層
凸克,包括各個不同的業(yè)務模塊议蟆,這些模塊之間的調(diào)用是通過路由實現(xiàn)的,不能存在引用關系的萎战,每個模塊會依賴一個上下文模塊
和項目配置模塊
咐容,上下文模塊
主要是管理用戶對象等用戶權限相關的事,項目配置模塊
主要是整體 App 的一些配置數(shù)據(jù)蚂维、以及主題顏色和一些第三方 key 的配置等(主要為了方便配置統(tǒng)一管理)戳粒。業(yè)務層是整個 App 的核心功能,而公用組件模塊
是跨業(yè)務虫啥、跨 App 的蔚约,不同的 App 之間是可以公用這些組件的,這一層最好作為公司級別的供大家所有人使用孝鹊。最下層為第三方庫炊琉,一般情況下我們需要對第三方做一層脫離耦合的封裝,以便我們在修改第三方時而不影響我們的業(yè)務模塊又活。整個項目從上到下為依賴關系苔咪,下層為上層提供功能服務。
業(yè)務模塊
模塊 | 介紹 | 地址 |
---|---|---|
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 簡單修改完成的舔清。
上下文模塊(Bella)
上下文模塊主要用于用戶對象的管理,后期會把考慮把本地緩存等加密功能加上曲初,上下文模塊被每個業(yè)務模塊所依賴体谒,用于管理用戶上下文對象,同步用戶信息的修改臼婆。
項目配置模塊(Alice)
包括項目的主題等各個模塊的配置抒痒,涉及所有業(yè)務模塊的主題顏色配置,以及一些第三方庫的 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
代表點擊重新加載的事件寂汇,經(jīng)過參數(shù)格式化、發(fā)送網(wǎng)絡請求捣染、數(shù)據(jù)解析等數(shù)據(jù)處理骄瓣,最后只需關注解析成功之后的 Model 數(shù)據(jù)然后更新 UI 界面。
公用模塊
公司的公用組件應該是長期積累的耍攘,不同的該功能榕栏,大部分是與業(yè)務無關的可以擴 App 或者夸業(yè)務使用的,經(jīng)過長時間的積累會慢慢完善蕾各,比如京東內(nèi)部有各種各樣的模塊組件扒磁,對與新開發(fā)一個項目來說會提高很多倍,這些公用組件模塊通過 CocoaPods 管理式曲,或者也可以通過 Framework 管理
以下是我個人積累的一些公用庫妨托,平常寫 Demo 啥的都是非常方便的
模塊 | 介紹 | 地址 |
---|---|---|
UtilCore | 基礎工具庫 | https://github.com/SeongBrave/UtilCore |
NetWorkCore | 網(wǎng)絡工具庫 | https://github.com/SeongBrave/NetWorkCore |
EmptyDataView | 列表為空時自定義展示空界面 | https://github.com/SeongBrave/EmptyDataView |
RxSwift 的使用
項目中大部分的邏輯處理是借助 RxSwift 實現(xiàn)的響應式編程,當界面上的每個操作都會轉(zhuǎn)換為一個信號然后通過對信號的各種加工網(wǎng)絡請求吝羞,到返回的數(shù)據(jù) JSON 解析以及錯誤對象的處理兰伤,感覺整個開發(fā)都是在開鑿水渠,等開發(fā)完了就不用管了钧排。
網(wǎng)絡請求
NetWorkCore通過對Alamofire簡單封裝敦腔,配合RxSwift可以很簡單的實現(xiàn)一個網(wǎng)絡請求,并且完成數(shù)據(jù)解析對應的 Mode 實體類恨溜,如下所示符衔,即可實現(xiàn)一個用戶登錄的網(wǎng)絡請求。
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: 請求參數(shù)
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)"
}
/// 直接通過路徑 和參數(shù)調(diào)整到 界面
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)
}
// 首先需要判斷跳轉(zhuǎn)的目標是否是界面還是處理事件 如果是界面需要: push 如果是事件則需要用:open
let isPushed = Navigator.that?.push(url) != nil
if isPushed {
return true
} else {
return (Navigator.that?.open(url)) ?? false
}
}
// 首先需要判斷跳轉(zhuǎn)的目標是否是界面還是處理事件 如果是界面需要: push 如果是事件則需要用:open
let isPushed = Navigator.that?.push(that) != nil
if isPushed {
return true
} else {
return (Navigator.that?.open(that)) ?? false
}
}
}
這塊其實可以更進一步的封裝判族,比如每次調(diào)整都可以通過正則表達式進行有效性的驗證,或者一些其他路由規(guī)則判斷
借助URLNavigator實現(xiàn)各個模塊的解耦项戴,理論上每個界面都可以實現(xiàn)互相跳轉(zhuǎn)的形帮,在處理商品列表界面的行點擊事件(didSelectRowAt
)的時候是由服務端返回的uri
字段決定的,具體跳轉(zhuǎn)哪個界面是有服務端決定的肯尺,個人的理解是界面負責產(chǎn)生信號,每個信號都會經(jīng)過復雜的篩選變化又會反應到界面上的躯枢,所有的跳轉(zhuǎn)事件都可以通過 URLNavigator
路由實現(xiàn)则吟,比如邏輯處理、界面跳轉(zhuǎn)等事件
每個模塊都有各自的模塊路由注冊類锄蹂,比如Jacob_router.swift
氓仲,包含了該模塊內(nèi)部所有的可路由的界面和事件處理的路由注冊,最后會在主模塊中統(tǒng)一注冊
錯誤處理
監(jiān)控整個 App 的所有錯誤,然后通過一些規(guī)則篩選最后展示給用戶是我們在開發(fā)一個 App 的時候需要考慮處理的敬扛,比如在下拉列表的時候晰洒,發(fā)送網(wǎng)絡請求,這時候網(wǎng)絡請求失敗了啥箭,需要界面上展示網(wǎng)絡錯誤谍珊,并且顯示重新加載的按鈕,或者是如果在調(diào)用相機獲取授權的時用戶沒有授權的時候急侥,需要提示給用戶授權相關的信息砌滞,等等這些邏輯處理都可以通過流的形式處理,在處理用戶網(wǎng)絡錯誤加載失敗的時候坏怪,通過 RxSwift
的一個很簡單的 Api:withLatestFrom
就能實現(xiàn)數(shù)據(jù)重新加載贝润,而不需要記住各種復雜的參數(shù)。
根據(jù)錯誤碼的不同進行不同的錯誤邏輯處理铝宵,如下代碼所示
/**
通過 mikerError 顯示錯誤信息
202024: 請登錄后再操作
- parameter error:
*/
public func toastError(_ error:MikerError){
if error.code == UtilCore.sharedInstance.toLoginErrorCode {
self.toastCompletion(error.message){ _ in
/**
* 在這塊 就是跳轉(zhuǎn)到登陸模塊,如果已經(jīng)跳轉(zhuǎn)就不需要直接忽略 否則 先將AppData.sharedInstance.isHasToLoginVc改為true然后再跳轉(zhuǎn)
*/
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 {
///表示是生產(chǎn)模式
let code = "\(error.code)"
if code.hasPrefix("2") {
self.toast(error.message)
} else {
self.toast(UtilCore.sharedInstance.errorMsg)
}
}
}
}
指令碼
與服務端確認配合確定打掘,通過錯誤碼
與路由
結合能達到一種指令碼的效果,客戶端取到服務端返回的錯誤碼
的時候先進行邏輯判斷鹏秋,適配一些規(guī)則尊蚁,如果符合則取服務端返回的uri
字段,直接進行路由跳轉(zhuǎn)拼岳,否則走錯誤處理拋出枝誊。這種指令碼
可以達到一些客戶端的跳轉(zhuǎn)邏輯交由服務端來控制,比如在注冊完畢之后是跳轉(zhuǎn)首頁還是繼續(xù)補充完詳細信息的這種需求是可以根據(jù)服務端返回的指令碼
來決定惜纸。
MVVM 架構設計
一直覺得南峰子翻譯的這兩篇文章挺不錯的雖然是 2014 的文章了叶撒,感興趣的可以看下
另外登陸注冊模塊(Carlisle)
是參考RxSwift官方 Demo 設計的,使用 MVVM 架構設計耐版,雖然沒有嚴格遵守上面文章所說的 MVVM 引用層次祠够,不過登陸注冊模塊(Carlisle)
還是可以靈活的適用于不同的需求的在簡單修改之后。
Gckit-CLI 的使用
CocoaPods 公共組件模塊可以很方便集成現(xiàn)有的模塊粪牲,但是我們每個業(yè)務都是完全不一樣的古瓤,每個接口返回的 JSON 文件也不一樣,然后我們得手動創(chuàng)建與之對應的 Model腺阳,這些操作完全沒有任何意義但是又是必須的落君,不過現(xiàn)在我們可以使用 Gckit-CLI 一鍵生成對應的所有 Model 實體類,我們只需要把對應的 JSON 文件放到對應的目錄即可亭引,Gckit-CLI 不僅可以生成 Model 文件绎速,ViewModel、ViewController焙蚓、View纹冤、Cell 等各種文件洒宝,并且是一鍵生成,大家可以嘗試使用下萌京,如果覺得可以的話麻煩給一個Star吧 ??雁歌。
Node.js 接口服務
twilight_app 為項目后臺的接口服務,一個客戶端開發(fā)的思維開發(fā)的后臺接口服務 ??知残,功能很簡單靠瞎,如果感興趣的可以下載看下
總結
本文簡單介紹了自己在 Swift 模塊化項目中的一些實踐經(jīng)驗,借助 RxSwift 實現(xiàn) MVVM 框架的設計橡庞,內(nèi)容比較雜较坛,供大家參考,隨著 Swift 5 的發(fā)布扒最,Swift ABI 的穩(wěn)定丑勤,相信會有更多團隊會選擇 Swift 語言開發(fā)自己的 App 的, 周圍認識的很多朋友都說如果嘗試過 Swift 之后就很難再回去用 Objective-C 了吧趣,Swift 本身帶有的很多特性是 Objective-C 不具有的法竞,呀感覺又扯遠了,我個人比較喜歡通過一些工具去實現(xiàn)一些效率方面的提升的强挫,通過模塊化實現(xiàn)代碼的復用岔霸,通過一些腳本工具實現(xiàn)重復無用代碼的自動生成,比如 Model 文件的生成等俯渤,這樣我們通過借助 CocoaPods 和 Gckit-CLI 結合使用呆细,使我們的開發(fā)效率大大提高了,節(jié)省出來的時間我們專注于業(yè)務功能的開發(fā)八匠。
?? 最后感謝您的閱讀!