Swift開發(fā)架構(gòu)及注意事項

一 : 簡單介紹以下幾個方面

1 編碼規(guī)范
2 設(shè)計模式的選擇
3 項目目錄結(jié)構(gòu)
4 網(wǎng)絡(luò)層
5 數(shù)據(jù)存儲
6 日志收集
7 安全性
8 測試(功能,性能等)和自動化打包(比如持續(xù)集成管理工具)

二 : 項目一般滿足點

  • 代碼整齊塞关,分類明確 ,沒有common,沒有core
  • 多聚合 , 少繼承 , 多協(xié)議;高內(nèi)聚 , 低耦合
(1) 功能內(nèi)聚和數(shù)據(jù)耦合绍填,是我們需要達成的目標凝化。
(2) 橫向的內(nèi)聚和耦合狭郑,通常體現(xiàn)在系統(tǒng)的各個模塊沛申、類之間的關(guān)系郁岩,
    而縱向的耦合祝迂,體現(xiàn)在系統(tǒng)的各個層次之間的關(guān)系睦尽。
(3) 少繼承, 多考慮協(xié)議
(4) 最終: 易測試,易拓展
  • 思路和方法要統(tǒng)一型雳,盡量不要多
  • 對公用方法該限制的地方有限制当凡,該靈活的靈活
  • 保持一定量的超前性
  • 接口少山害,接口參數(shù)少, 方法語義明確
  • 高性能

三 : 編碼規(guī)范

詳見: Swift編碼規(guī)范

補充:

1 避免用OC式的語法寫Swift : 舉例賦值字符串操作,為空設(shè)置默認值

  • OC式的語法寫Swift
 var nameString  : String
 var newNameString : String?
 newNameString = nil

 if (newNameString != nil) {//不為空賦值
      nameString = newNameString!
 } else {
     nameString = "昵稱"
 }
  • Swift語法寫法:
if let nameStr = newNameString { //解包成功賦值
    nameString = nameStr
} else {
     nameString = "昵稱"
}
  • 簡潔優(yōu)雅的使用Swift:
nameString = newNameString ?? "昵稱"

2 充分利用Swift的新特性
能用協(xié)議的盡量, 少用繼承, 這樣有利于項目擴展和分工合作等等

3 個人習(xí)慣,能用code少用storyboard 和 xib

當(dāng)團隊具有一定規(guī)模的(比如iOS開發(fā) > 5 人),storyboard 和 xib缺點和明顯
同一份代碼文件的作者會有很多,不同作者同時修改同一份代碼的情況也不少見沿量。
因此浪慌,使用Git進行代碼版本管理時出現(xiàn)Conflict的幾率也比較大。
需求變化非常頻繁朴则,為了完成需求而針對現(xiàn)有代碼進行微調(diào)的情況权纤,以及針對現(xiàn)有代碼的部分復(fù)用的情況
復(fù)雜界面元素、復(fù)雜動畫場景的開發(fā)任務(wù)

4代碼注釋

/**
 函數(shù)描述信息,支持換行(空一行作為換行符)
 
 描述支持Markdown語言:
 
 - 信息1
 - 信息2
 - 信息3
 
 1. item 1
 2. item 2
 3. item 3
 
(下面的'/'實際代碼中去掉)
 /```
 let a = "Swift"
 let b = "代碼注釋"
 print( a + ", " + b )
 /```
 
 */
func markdownMsg() {}


/// 支持常用類型描述,比如參數(shù) **Parameter**, 錯誤信息 **Throws** 和返回信息 **Returns**.
///
/// - Parameters:
///   - Param1: 參數(shù)1
///   - Param2: 參數(shù)2
///
/// - Throws: 條件不滿足時返回的錯誤類型
///
/// - Returns: 正常執(zhí)行返回的結(jié)果
func parameterMsg(Param1: AnyObject?, Param2: AnyObject?) throws -> String {
    if Param1 == nil && Param2 == nil{
        throw MsgError.BothNilError
    }
    return "result"
}
enum MsgError: Error{
    case BothNilError
}


按住option點擊方法:(還有其他注釋如 MARK 等)


圖片.png
圖片.png

四 : 設(shè)計模式的選擇 : MVVM ( MVC佛掖、MVCS妖碉、VIPER )

目前iOS常用的幾種設(shè)計模式:代理模式、觀察者模式芥被、單例模式欧宜、策略模式、工廠模式拴魄、MVC模式等

MVC模式
當(dāng)一個項目越來越大的時候,純粹的MVC模式會變得越來越難維護了,所以有必要對其進行改進
一般項目可以考慮使用MVVM

View <-> Controller <-> ViewModel <-> Model

附: MVVM模式配合使用框架RXSwift 和RXcocoa開發(fā),代碼會更簡潔

RX學(xué)習(xí)資料:航歌hangge.com

1 簡單聊點 依賴 問題:

從A頁面push到B頁面:

mineVC.title = "個人主頁"
mineVC.ID = personID
mineVC.isMe = isMe
self!.navigationController?.pushViewController(mineVC, animated: true)

如果以后需求改變了,需要增加幾個參數(shù)或者是刪除參數(shù), 如此改動會很麻煩
那么這些操作其實不必要Controller來做:

PushModel : 專門用來管理需要被push或者present事件
BDataModel : 專門聲明需要傳遞的參數(shù),根據(jù)需要進行擴展

如此,控制器和控制器之間不會過度依賴,只需要告訴PushModel我有個BDataModel給你就行

2 簡單聊聊高頻使用的UITableViewCell,通過cellForRowAt方法如何瘦身來理解如何拆分

(1) 瘦身前的控制器: 顯得異常臃腫

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let aNews = news[indexPath.row]
        switch aNews.cell_type {
        case .user:             // 用戶
            let cell = tableView.ym_dequeueReusableCell(indexPath: indexPath) as HomeUserCell
            cell.readCountLabel.text = "\(aNews.readCount)閱讀"
            cell.verifiedContentLabel.text = aNews.verified_content
            cell.digButton.setTitle(aNews.diggCount, for: .normal)
            cell.commentButton.setTitle("\(aNews.commentCount)", for: .normal)
            cell.feedshareButton.setTitle(aNews.forwardCount, for: .normal)
            cell.contentLabel.attributedText = aNews.attributedContent
            //cell.aNews = aNews
            return cell
        case .relatedConcern:   // 相關(guān)關(guān)注
            let cell = tableView.ym_dequeueReusableCell(indexPath: indexPath) as TheyAlsoUseCell
            cell.readCountLabel.text = "\(aNews.readCount)閱讀"
            cell.verifiedContentLabel.text = aNews.verified_content
            cell.digButton.setTitle(aNews.diggCount, for: .normal)
            cell.commentButton.setTitle("\(aNews.commentCount)", for: .normal)
            cell.feedshareButton.setTitle(aNews.forwardCount, for: .normal)
            cell.contentLabel.attributedText = aNews.attributedContent
            cell.theyUse = aNews.raw_data
            return cell
        }
    }

(2) 瘦身后的控制器 : 通過view的set方法,把賦值代碼移動到cell里,或者建立一個DataModel來處理這件事情.

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let aNews = news[indexPath.row]
        switch aNews.cell_type {
        case .user:             // 用戶
            let cell = tableView.ym_dequeueReusableCell(indexPath: indexPath) as HomeUserCell
            cell.aNews = aNews
            return cell
        case .relatedConcern:   // 相關(guān)關(guān)注
            let cell = tableView.ym_dequeueReusableCell(indexPath: indexPath) as TheyAlsoUseCell
            cell.theyUse = aNews.raw_data
            return cell
        }
    }

上面代碼仍然有一定的冗余,兩個cell進行了兩次初始化,如果十個呢,顯然會越來越臃腫.

(3) 進一步瘦身后的控制器 : 控制器只需要對cell進行一次賦值就行,具體什么cell讓cell的DataModel去做就行了.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let row = fromData[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: row.reuseIdentifier, for: indexPath)
        row.update(cell: cell)
        return cell
    }
3 簡單聊聊Swift協(xié)議編程 [ 協(xié)議的延展以及和結(jié)構(gòu)體配合 ]
  • Swift is a Protocol-Oriented Programming Language( Swift 是一門面向協(xié)議 (POP) 開發(fā)的語言)

  • Swift 的核心是面向協(xié)議編程

  • 面向?qū)ο笈c面向協(xié)議比較:**

面向?qū)ο笫且粋€很傳統(tǒng)的軟件開發(fā)模式冗茸,通過類來實現(xiàn)
面向協(xié)議是蘋果在 swift 中主推的,通過協(xié)議和結(jié)構(gòu)體匹中,可以代替絕大部分的類,面向協(xié)議使代碼更加靈活夏漱,類似于組件化開發(fā),符合工廠方法模式

  • Swift 中的很多對象都改成了結(jié)構(gòu)體和協(xié)議

Classes : 6
Enum : 8
Struct : 103
Protocol : 86

  • 回顧一下OC的單繼承:

1 子類是通過繼承父類獲得父類的方法
2 通過繼承添加的方法顶捷,子類不一定每個都會用挂绰,使代碼冗余
3 擁有太多的子類, 類冗余
4 過度依賴父類的方法,當(dāng)父類的方法更改后服赎,影響子類的重載方法
5 不能多繼承

  • Swift協(xié)議編程的幾個點

1 想要添加一些屬性和方法,給需要的地方, 組件化
2 為協(xié)議可以添加屬性 和 方法 protocol xxxProtocol{}
3 接受協(xié)議的結(jié)構(gòu)體 struct xxxModel: xxxProtocol {}
4 對協(xié)議進行擴展 extension xxxProtocol {}
5 協(xié)議的協(xié)議 protocol xxxProtocol xxxProtocol{}

  • 協(xié)議的使用 :類,協(xié)議1,協(xié)議2,協(xié)議3, .. {}
class ViewController: UIViewController, AProtocol, BProtocol, CProtocol {...}
4 簡單聊聊擴展 extension

1 可以協(xié)議,自定義對類,系統(tǒng)類 等等 進行屬性和方法延展
比如對系統(tǒng)類UIViewController添加屬性和方法
2 如果類和協(xié)議等進行了擴展,那么子類或者協(xié)議遵循者就自動擁有該擴展功能
3 擴展一般是基礎(chǔ)屬性和功能,比如class動物,那么一定擁有eat的功能,但是不一定有fly(那么fly就可使用協(xié)議)
4 對同一個類擴展相同的方法和屬性,那么就會導(dǎo)致沖突,可以使用命名空間解決

extension UIViewController {
    private struct YXVCKey {
        static var sKey = "dataSourceKey"
    }
    //為系統(tǒng)類添加屬性
    public var dataSource : Array<Any> {
        get {return (objc_getAssociatedObject(self, &YXVCKey.sKey) as? Array<Any>)! }
        set { objc_setAssociatedObject(self, &YXVCKey.sKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }
    
    //為類添加方法
    public func changeViewColor(_ bgColor :UIColor) -> () {
        self.view.backgroundColor = bgColor
    }

}

五 : 項目目錄結(jié)構(gòu) : 根據(jù)不同項目需求決定

  *  Base : 存放一些基類,比如BaseViewController,BaseModel等,共性直接在基類中去修改
  *  Vendor : 三方,因為我的項目中使用cocopods管理三方,所以這個文件夾中我在此放的是一些比較小的功能的第三方或者不支持cocopods的
  *  Framework : 存放一些類庫或者自己封裝的一些靜態(tài)庫
  *  Resource : 存放app中一些索引資源,比如圖片,文本等,或者將圖片打包的Bundle
  *  Custom : 這個文件夾我用來存放自己項目或者公司自己風(fēng)格的一些自定義的視圖框架( 或SDK )
  *  Network : 這個只專門用來做網(wǎng)絡(luò)處理的,因為這個項目基本上都會用到網(wǎng)絡(luò)請求,算是比較重要的一個部分,所以在此單獨拿出來作為一個分類
  *  Expand : 擴展文件
      -- Tool : 工具
      -- DataBase: 數(shù)據(jù)存儲相關(guān)
      -- Macros : 宏定義
      -- Const : 常量
  *  Main : AppDelegate或者AppDelegate的Category 等等
  *  Class : 存放的是App中所有的模塊功能
       - Public : 公用
       - Home : 比如首頁
             -- Controller
             -- Model
             -- View
             -- ViewModel
             -- Other

 * XXX-Tests :        單元測試
 * XXX-UITests  :   UI測試
 * Products :         系統(tǒng)自動生成的.app所在文件夾
 * Pods  :               Pods第三方庫

六 : 網(wǎng)絡(luò)層

Alamofire : 首選主流網(wǎng)絡(luò)開源框架
Moya
RXSwift / RXCocoa

pod "Moya/RxSwift"

以下是藝下NetWork.swift 示例代碼:
import Foundation
import Moya
public enum NetWork {
    case login(mobile:String,captcha:String)
}

extension NetWork:TargetType{
    public var sampleData: Data {
        return Data.init()
    }
    
    /// The target's base `URL`.
    public var baseURL: URL {
        return API.base.rawValue.url!
    }
    
    // The path to be appended to `baseURL` to form the full `URL`.
    public var path: String {
        switch self {
        case .login(mobile: _, captcha: _):
            return "/auth/login";
        }
    }
    
    // The HTTP method used in the request.
    public var method: Moya.Method {
        switch self {
        case .login(mobile: _, captcha: _):
            return .get;
        }
    }

    // The type of HTTP task to be performed.
    public var task: Task {return .requestPlain}

    // The type of validation to perform on the request. Default is `.none`.
    public var validationType: ValidationType { return .none }
    
    // The headers to be used in the request.
    public var headers: [String: String]? {
        var AlamHeaders = [*****]
        AlamHeaders["sign"] = ***
        (一堆設(shè)置)
        return AlamHeaders
    }
}

七 : 數(shù)據(jù)存儲
根據(jù)需求決定持久化方案
持久層與業(yè)務(wù)層之間的隔離
持久層與業(yè)務(wù)層的交互方式
數(shù)據(jù)遷移方案
數(shù)據(jù)同步方案
損壞修復(fù)
等等
目前移動端數(shù)據(jù)庫方案按其實現(xiàn)可分為兩類葵蒂,

一 : 關(guān)系型數(shù)據(jù)庫,代表有CoreData重虑、FMDB等践付。
CoreData
它是蘋果內(nèi)建框架,和Xcode深度結(jié)合缺厉,可以很方便進行ORM永高;但其上手學(xué)習(xí)成本較高,不容易掌握提针。穩(wěn)定性也堪憂命爬,很容易crash;多線程的支持也比較雞肋辐脖。
SQLite
FMDB
它基于SQLite封裝遇骑,對于有SQLite和ObjC基礎(chǔ)的開發(fā)者來說,簡單易懂揖曾,可以直接上手落萎;而缺點也正是在此,F(xiàn)MDB只是將SQLite的C接口封裝成了ObjC接口炭剪,沒有做太多別的優(yōu)化练链,即所謂的膠水代碼(Glue Code)。使用過程需要用大量的代碼拼接SQL奴拦、拼裝Object媒鼓,并不方便。

二 : key-value數(shù)據(jù)庫错妖,代表有Realm绿鸣、LevelDB、RocksDB等暂氯。
Realm
1 因其在各平臺封裝潮模、優(yōu)化的優(yōu)勢,比較受移動開發(fā)者的歡迎痴施。對于iOS開發(fā)者擎厢,key-value的實現(xiàn)直接易懂,可以像使用NSDictionary一樣使用Realm辣吃。并且ORM徹底动遭,省去了拼裝Object的過程。
2 是一個跨平臺的移動數(shù)據(jù)庫: Java神得,Objective-C厘惦,Swift,React Native哩簿,Xamarin 等等
3 Realm支持事務(wù)宵蕉,滿足ACID:原子性(Atomicity)、一致性(Consistency)卡骂、隔離性(Isolation)国裳、持久性(Durability)。

三 : WCDB 是微信推出的一個高效全跨、完整缝左、易用的移動數(shù)據(jù)庫框架,基于SQLCipher浓若,支持iOS, macOS和Android渺杉。易用,支持事務(wù)挪钓,可加密是越、損壞修復(fù)。WCDB在迭代中也在慢慢的穩(wěn)定

Realm碌上、WCDB與SQLite移動數(shù)據(jù)庫性能對比測試:

image
image
目前比較好的是Realm 和 微信的 WCDB ,任意一個都可以,可根據(jù)項目自己選擇 .

附:Realm的簡單使用

//創(chuàng)建數(shù)據(jù)管理對象
let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "TemporaryRealm"))
// 數(shù)據(jù)持久化操作(類型記錄也會自動添加的)
try! realm.write {
realm.add(self.userInfo)
}
//數(shù)據(jù)庫地址
print(realm.configuration.fileURL ?? "")

//獲取模型數(shù)據(jù)
var consumeItems:Results<UserInfoModel>?
consumeItems = realm.objects(UserInfoModel.self)
let testType1 = consumeItems?.first
print(testType1?.data?.refreshToken ?? "failue")

八 : 日志收集

一個 APP不可避免的要對其進行,bug收集和日志分析

1 一種方式是發(fā)生崩潰以后上傳日志到自己的服務(wù)器,然后進行分析
XCGLogger

2 另外一種是第三方接口, 比如bugly的日志收集管理系統(tǒng) : 功能比較強大,不僅僅是崩潰日志,甚至是卡頓等都能收集,并且有可視化的管理系統(tǒng)方便查看和分析

根據(jù)業(yè)務(wù)需要選擇一個合適的方式.

九 : 安全性

  1. 對修改請求進行加密認證,修改使用POST ,DELETE 等 提交表單
    展示和分享相關(guān)頁面使用GET請求,方便分享,收藏等等
  2. 有條件使用https
  3. 對用戶敏感數(shù)據(jù) 使用密鑰進行加密
  4. 本地配置文件中的敏感數(shù)據(jù)進行加密 或 保存在keychain中
    等等

十 : 自動化打包和測試,持續(xù)集成工具

測試是對項目健全性能的保障:包括 性能倚评、內(nèi)存weak浦徊、UnitTest,AutoTest 天梧、UITest 等等

  • UnitTest 單元測試 分為3種:
    1 邏輯測試:測試邏輯方法. --邏輯測試
    2 異步測試:測試耗時方法(用來測試包含多線程的方法): --異步測試
    3 性能測試:測試某一方法運行所消耗的時間 --性能測試

  • UI 自動化測試 :
    iOS 自動化測試 UI Automation 【xcode 8 找不到 Automation 盔性?】

  • 代碼版本管理 : 持續(xù)集成工具
    例如 CruiseControL,hudson 呢岗,jenkins冕香,還有apache的Continuum 等 開源的持續(xù)集成工具
    jenkins 配置

十一 : 幾點注意事項

  • 注意內(nèi)存泄漏的情況: 比如循環(huán)強引用,閉包等
  • 適配常規(guī)機型
  • 本地低頻使用的大圖盡量不要使用imageName加載到緩存
  • 圖片加載保存流程圖 :


    圖片加載保存流程圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市后豫,隨后出現(xiàn)的幾起案子悉尾,更是在濱河造成了極大的恐慌,老刑警劉巖挫酿,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件构眯,死亡現(xiàn)場離奇詭異,居然都是意外死亡饭豹,警方通過查閱死者的電腦和手機鸵赖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拄衰,“玉大人它褪,你說我怎么就攤上這事∏滔ぃ” “怎么了茫打?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長妖混。 經(jīng)常有香客問我老赤,道長,這世上最難降的妖魔是什么制市? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任抬旺,我火速辦了婚禮,結(jié)果婚禮上祥楣,老公的妹妹穿的比我還像新娘开财。我一直安慰自己,他們只是感情好误褪,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布责鳍。 她就那樣靜靜地躺著,像睡著了一般兽间。 火紅的嫁衣襯著肌膚如雪历葛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天嘀略,我揣著相機與錄音恤溶,去河邊找鬼乓诽。 笑死,一個胖子當(dāng)著我的面吹牛宏娄,可吹牛的內(nèi)容都是我干的问裕。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼孵坚,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窥淆?” 一聲冷哼從身側(cè)響起卖宠,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忧饭,沒想到半個月后扛伍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡词裤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年刺洒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吼砂。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡逆航,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出渔肩,到底是詐尸還是另有隱情因俐,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布周偎,位于F島的核電站抹剩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蓉坎。R本人自食惡果不足惜澳眷,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛉艾。 院中可真熱鬧钳踊,春花似錦、人聲如沸伺通。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罐监。三九已至吴藻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弓柱,已是汗流浹背沟堡。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工侧但, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人航罗。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓禀横,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粥血。 傳聞我的和親對象是個殘疾皇子柏锄,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容