
最近被公司那個(gè)架構(gòu)松散,底層混亂,缺少規(guī)范的代碼煩死了,決定把公司的項(xiàng)目重新弄一個(gè),將之前的MVC改成MVVM,并將代碼有OC遷移到Swift,搭建新項(xiàng)目的iOS框架,一個(gè)緊湊高效的App框架,可以為你以后的代碼路省下很多麻煩。
前銀聯(lián)移動(dòng)支付首席產(chǎn)品架構(gòu)師這樣說(shuō):
我做(開(kāi)發(fā))架構(gòu)的幾個(gè)原則,根據(jù)優(yōu)先次序高低排列:1. (邏輯)拆分越細(xì)越好 2. 依賴關(guān)細(xì)越少越好 3. 交互越少越好 ... 相互矛盾時(shí),如果沒(méi)有特殊理由,以優(yōu)先權(quán)高者勝出。
一、Idea
首先根據(jù)產(chǎn)品需求和設(shè)計(jì)圖,腦中先建立一個(gè)產(chǎn)品架構(gòu)(當(dāng)然你最好就是給它弄一個(gè)思維導(dǎo)圖,我比較推薦XMind和MindNode):
-
MVVM or MVC?
架構(gòu)和建筑很相似,只是前者會(huì)隨著時(shí)間的推移進(jìn)行演變,好的架構(gòu)催生好的代碼,就像趕緊整潔的房子里,會(huì)想到其家具、電器、擺設(shè)等都是很有條理的。
MVC:模式簡(jiǎn)單,標(biāo)準(zhǔn)粗放,容易滋生捷徑;
MVVM: 某種程度上比MVC好,但是很多場(chǎng)景沒(méi)有覆蓋到,比如缺少頁(yè)面間的跳轉(zhuǎn)/通信、數(shù)據(jù)獲取等。
(還有種很少用的VIPER:細(xì)致,但是臃腫)
那么問(wèn)題來(lái)了:這么多系統(tǒng)框架,我改選哪個(gè)?
個(gè)人覺(jué)得,無(wú)論哪種都是可以的,關(guān)鍵是要簡(jiǎn)潔、結(jié)構(gòu)清晰、職責(zé)明確、符合GUI編程的特點(diǎn)。
這次我要說(shuō)的是來(lái)自微軟的MVVM:
MVVM
ViewModel:相對(duì)于MVC引入的試圖模型。是試圖顯示邏輯、驗(yàn)證邏輯、網(wǎng)絡(luò)請(qǐng)求等代碼存放的地方。任何試圖本身的引用都不應(yīng)該放在ViewModel中。這樣解決的Controller的臃腫,不用什么邏輯,網(wǎng)絡(luò)的東西都丟給試圖控制器來(lái)完成,使其耦合性降低
- 讓你的代碼覆蓋更多的場(chǎng)景
根據(jù)你的產(chǎn)品的類(lèi)型,預(yù)計(jì)未來(lái)的需求,比如IM:語(yǔ)音,視頻,音樂(lè):播放器,放小點(diǎn)范圍,比如一個(gè)個(gè)人中心頁(yè)面,你就要考慮很多東西,比如頭像,而頭像你要考慮上傳照片等等,需求會(huì)越來(lái)越多,但這都無(wú)所謂,只要你的框架包容性比較強(qiáng),要不然越到后面,你就忍不住要重寫(xiě)你的代碼了。

二、搭建目錄結(jié)構(gòu)

1.應(yīng)用入口
1.AppDelegate是應(yīng)用的代理,應(yīng)用級(jí)的事件都委托它處理,包含啟動(dòng)退出、推送等事件,以及IM、支付等第三方的回調(diào),這使得AppDelegate內(nèi)代碼龐大,錯(cuò)綜復(fù)雜,十分不利于閱讀和維護(hù),swift里面有一個(gè)extension,學(xué)會(huì)使用這個(gè)讓你的代碼看起來(lái)更加有條理。

2.功能模塊
- View可以獨(dú)立于Model變化和修改,一個(gè)ViewModel可以綁定到不同的"View"上,當(dāng)View變化的時(shí)候Model可以不變,當(dāng)Model變化的時(shí)候View也可以不變。
-
Model對(duì)應(yīng)服務(wù)端的 Objects。
數(shù)據(jù)模型示例
3.ViewModel我們處理業(yè)務(wù)邏輯的核心層,在這里我們需要發(fā)起網(wǎng)絡(luò)請(qǐng)求(如果網(wǎng)絡(luò)請(qǐng)求較多,可以抽出來(lái),只在ViewModel里調(diào)用)、解析數(shù)據(jù)、轉(zhuǎn)換數(shù)據(jù)給前端,還有就是一些邏輯的處理,比如下拉刷新的控制等等。
3.工具類(lèi)
- Tools文件夾內(nèi)主要包含全局通用工具,來(lái)源于對(duì)三方框架的二次封裝,或是自己寫(xiě)的工具類(lèi)。在這個(gè)項(xiàng)目里,我封裝了網(wǎng)絡(luò)請(qǐng)求工具,下拉刷新,一些控件的擴(kuò)展,還有一些空間的封裝等。
4.基類(lèi)
- Base文件夾用來(lái)存放項(xiàng)目的基類(lèi),基類(lèi)作用包含一些定制化的內(nèi)容,例如頁(yè)面樣式,空數(shù)據(jù)頁(yè)面等,使用基類(lèi)來(lái)實(shí)現(xiàn),可以統(tǒng)一控制,利于維護(hù),減少冗余,也為更清晰。
5.Pods三方管理
- CocoaPods是iOS項(xiàng)目的依賴管理工具,開(kāi)發(fā)iOS項(xiàng)目不可避免地要使用第三方開(kāi)源庫(kù),CocoaPods的出現(xiàn)使得我們可以節(jié)省設(shè)置和第三方開(kāi)源庫(kù)的時(shí)間。網(wǎng)絡(luò)請(qǐng)求:AFNetworking、Alamofire;自動(dòng)布局:Masonry、SnapKit;數(shù)據(jù)庫(kù):Realm、FMDB;解析數(shù)據(jù):YYModel、SwiftyJSON;圖片處理:SDWebImage。
還有很多優(yōu)秀的三方庫(kù),可以上github搜搜,沒(méi)事看一下大神些的三方庫(kù)也是受益匪淺的。
程序猿長(zhǎng)得可以保守,思想一定不能太保守。代碼還是不要重復(fù)造輪子,有現(xiàn)成的就用現(xiàn)成的。
6.橋接文件
由于用swift來(lái)寫(xiě)代碼,但是有時(shí)候你也要用到OC的三方庫(kù),這個(gè)時(shí)候你就需要用到橋接文件來(lái)進(jìn)行混編了,步驟很簡(jiǎn)單:
- common+n選擇Header File
- targest->buildsetting->搜索bridg
- 更改Objective-C Bridging Header的值,格式為:項(xiàng)目名/header文件名.h
- 在你的橋接文件里面導(dǎo)入OC文件的頭文件,這樣你就可以在你的項(xiàng)目里面愉快的使用OC方法了
7.封裝網(wǎng)絡(luò)工具
這里我用AFNetworking來(lái)進(jìn)行的一次封裝的網(wǎng)絡(luò)單例,還可以對(duì)齊進(jìn)行二次封裝,具體根據(jù)和后臺(tái)的通信來(lái)處理
class YanNetworkManager: AFHTTPSessionManager {
/// 靜態(tài)區(qū)/常量/閉包
/// 在第一次訪問(wèn)時(shí),執(zhí)行閉包,并且將結(jié)果保存在shared中
static let shared: YanNetworkManager = {
// 實(shí)例化對(duì)象
let instance = YanNetworkManager()
// 設(shè)置響應(yīng)反序列化支持的數(shù)據(jù)類(lèi)型
instance.responseSerializer.acceptableContentTypes?.insert("text/plain")
return instance
}()
/// 使用一個(gè)函數(shù)封裝 AFN 的 GET / POST 請(qǐng)求
///
/// - Parameters:
/// - method: GET/POST
/// - URLString: URLString
/// - parameters: 參數(shù)字典
/// - completion: 完成回調(diào)(json,是否成功)
func request(method: WBHTTPMethod = .GET, URLString: String, parameters: Any?, completion: @escaping (_ json: Any?, _ isSuccess: Bool)->()) {
// 成功回調(diào)
let success = { (task: URLSessionDataTask, json: Any?) in
completion(json, true)
}
// 失敗回調(diào)
let failure = { (task: URLSessionDataTask?, error: Error) in
if (task?.response as? HTTPURLResponse)?.statusCode == 403 {
print("Token 過(guò)期了")
// 發(fā)送通知(本方法不知道被誰(shuí)調(diào)用,誰(shuí)接收到通知,誰(shuí)處理?。? NotificationCenter.default.post(name: NSNotification.Name(rawValue: WBUserShouldLoginNotification), object: "bad token")
}
print("網(wǎng)絡(luò)請(qǐng)求錯(cuò)誤 \(error)")
completion(nil, false)
}
if method == .GET {
get(URLString, parameters: parameters, progress: nil, success: success, failure: failure)
} else {
post(URLString, parameters: parameters, progress: nil, success: success, failure: failure)
}
}

