Swifty Tips ??

image.png

Swift 開發中的一些小的技巧

剛開始的時候, 特別好奇大廠是怎么搞的, 他們的項目長什么樣子, 他們用哪些庫...想在巨人的肩膀上開發, 免得浪費時間在那些已經有很好解決方案的事情上。

四年前,我和團隊中很多很厲害的人討論過一些編程實踐。今天就分享一些東西吧。

歡迎指正!??

濫用引用類型

只有“動態”對象才使用引用類型。這里的“動態”對象是什么呢?看下面的代碼:

struct Car {
    let model: String
}
class CarManager {
    private(set) var cars: [Car]
    func fetchCars() {}
    func registerCar(_ car: Car) {}
}

?? 在這里只是一個值。他代表的就是一些數據。就像 123。 這種數據是“靜態”的數據(死的)。 它不會處理任何東西, 所以它也沒有必要是“動態”的, 也就是說, 沒必要把它定義成引用類型。

另一方面:

CarManager 就需要是一個“動態”的對象。因為這個對象會發起網絡請求, 然后將請求結果保存起來。在值類型對象中是不能執行異步任務的, 因為他們是“靜態”的數據。我們需要的 CarManager 對象在一定的范圍內是應該是動態的, 他會請求數據, 也會注冊新的 Car

這個主題完全可以寫一篇文章來深入。推薦看看 Andy Matuschak 的文章, 和 WWDC

隱式解包可選類型(!)

默認不要隱式解包可選類型。 在大多數場景中你都可能會忘掉這件事情。但是在一些特殊情況下應該這樣做來減少編譯器的壓力。而且我們也需要去理解這件事情背后的邏輯。

基本上, 如果這個屬性在初始化的過程中必須為 nil 但是之后就會被賦值, 就可以定義這個屬性為 optional。因為你肯定不會在賦值之前訪問這個屬性, 如果編譯器一直警告這個值可能為 nil 真的挺討厭的。

看看xib中拖出來的屬性:

class SomeView: UIView {
    @IBOutlet let nameLabel: UILabel
}

如果這樣定義的話, 編譯器就會讓你在初始化方法中給nameLabel賦值。因為這行代碼告訴編譯器這個 View 無論什么時候都有 nameLabel。 但是, 有病啊!肯定不能這么干啊。因為其實在 initWithCoder 中已經幫我們實現了 xib 中的 label 和這個屬性之間的關聯。明白了嗎? 這個值永遠都不可能為空, 就沒有必要判斷這個東西是不是存在了。所以也不需要去賦值了啊。

你:這玩意兒肯定不可能是空, 別瞎幾把報錯了
編譯器: 好的!

class SomeView: UIView {
    @IBOutlet var nameLabel: UILabel!
}

Q: 在dequeue一個tableviewCell 的時候能不能(!)?
A: 還是不要吧!至少給一個 Crash 啊

guard let cell = tableView.dequeueCell(...) else {
    fatalError("Cannot dequeue cell with identifier \(cellID)")
}

濫用 AppDelegate

AppDelegate 不是拿來給你做保存全局變量的容器的(全局屬性、工具方法、管理類等等。)他只是一個用來實現一些協議的類而已。放過它吧!

applicationDidFinishLaunching 方法里肯定都會做一些很重要的事情, 但是當項目不斷變大的時候這種情況很容易變的很恐怖。創建新的類(文件)來做這些事情吧!

?? Don’t:

let persistentStoreCoordinator: NSPersistentStoreCoordinator
func rgb(r: CGFloat, g: CGFloat, b: CGFloat) -> UIColor { ... }
func appDidFinishLaunching... {
    Firebase.setup("3KDSF-234JDF-234D")
    Firebase.logLevel = .verbose
    AnotherSDK.start()
    AnotherSDK.enableSomething()
    AnotherSDK.disableSomething()
    AnotherSDK.anotherConfiguration()
    persistentStoreCoordinator = ...
    return true
}

?? Do:

func appDidFinishLaunching... {
    DependencyManager.configure()
    CoreDataStack.setup()
    return true
}

默認參數

給一個方法的某些參數設置默認值是非常方便的事情。如果沒有這個特性的話, 可能就需要給同一個功能寫好幾個方法了。像下面一樣:

func print(_ string: String, options: String?) { ... }
func print(_ string: String) {
  print(string, options: nil)
}

如果有默認參數值, 就可以是這樣的:

func print(_ string: String, options: String? = nil) {...}

很簡單對吧! 給自定義 UI 組件設置默認顏色、提供默認的參數、給網絡請求添加默認的超時時間等等。但是, 使用這個語法糖在遇到依賴注入的時候就要小心了。

看下面的例子:

class TicketsViewModel {
    let service: TicketService
    let database: TicketDatabase
    init(service: TicketService,
       database: TicketDatabase) { ... }
}

在 App target:

let model = TicketsViewModel(
  service: LiveTicketService()
  database: LiveTicketDatabase()
)

在 Test target:

let model = TicketsViewModel(
    service: MockTicketService()
    database: MockTicketDatabase()
)

在這里使用協議的原因就是把這些功能從具體的類中抽象出來。這就使得你可以向這個 viewModel 中注入任何你想要的具體實現。 如果這里你把 LiveTicketService 作為默認的參數, 這就使得TicketsViewModel 依賴了 LiveTicketService這么一個具體的類型。這跟最初想要達到的目的有了一些沖突。

現在沒那么方便了吧?

想象一下在你 App 還有 Test 兩個 target 中。 TicketsViewModel 會被同時添加到兩個 target 中, 然后把 LiveTicketServiceMockTicketService 分別添加。如果 TicketsViewModel添加了對 LiveTicketService 的依賴。 Test target 肯定就編譯不過了。

可變參數函數

這... 就是很爽啊!

func sum(_ numbers: Int...) -> Int {
    return numbers.reduce(0, +)
}
sum(1,2)      // 3
sum(1,2,3)    // 6
sum(1,2,3,4)  // 10

使用類型嵌套

Swift 支持內部類。所以有用就可以這么做:

?? Don’t:

enum PhotoCollectionViewCellStyle {
    case default
    case photoOnly
    case photoAndDescription
}

這個枚舉可能在 PhotoCollectionViewCell 之外就不會再使用到了。沒理由把這個枚舉聲明成全局的。

?? Do:

class PhotoCollectionViewCell {
    enum Style {
        case default
        case photoOnly
        case photoAndDescription
    }
    let style: Style = .default
}

這很容易理解, 畢竟 Style 本來就是用來標記 PhotoCollectionViewCell 的。而且還少了23個字符呢。

使用 final 關鍵字 ??

如果你不需要拓展某些類, 也不希望這些類被拓展, 使用 final 修飾它。不用擔心犯錯, 比如 PhotoCollectionViewCell 這個類, 你還有可能繼承它嗎?

而且:這么做可以節約編譯時間。

給常量命名空間

在 OC 中是通過在全局的常量前面加 PFX 或者 k 來給這些常量命名空間的。但是 Swift 可不這樣。

?? Don’t:

static ket kAnimationDuration: TimeInterval = 0.3
static let kLowAlpha = 0.2
static let kAPIKey = "13511-5234-5234-59234"

?? Do:

enum Constant {
    enum UI {
        static let animationDuration: TimeInterval = 0.3
        static let lowAlpha: CGFloat = 0.2  
    }
    enum Analytics {
        static let apiKey = "13511-5234-5234-59234"
    }
}

我個人的偏好是使用 C 來代替 Constant, 他已經夠清晰了。這個可以看你自己喜歡了。

Before: kAnimationDuration 或者 kAnalyticsAPIKey
After: C.UI.animationDuration 或者 C.Analytics.apiKey

_ 的使用

_ 是對沒有使用到的變量的占位符。他就是告訴編譯器"這個值是什么不重要"。 不然編譯器會有警告??。

?? Don't

if let _ = name {
    print("Name is not nil.")
}

optional就像一個盒子。可以直接看他是不是空的, 沒必要每次都把里面的東西拿出來。

?? Do:

  • 判空
if name != nil {
    print("Name is not nil.")
}
  • 返回值沒用
_ = manager.removeCar(car) // 成功返回true
  • ConpletionHandler
service.fetchItems {data, error , _ in
    // 第三個參數我不在乎他是什么
}

方法命名

這點適用于所有需要人類去閱讀的語言。代碼總是不那么容易理解的, 不要浪費別人的精力。

driver.driving()

這是在干什么?

  • 是把 driver 標記成 driving 狀態?
  • 還是檢查 driver 是不是 driving 狀態, 并且返回一個 bool 值?

如果要點進去看才知道這方法是干什么的, 這個命名就是失敗了。多人協同開發或者處理遺留項目的時候, 你讀別人代碼的時間比你寫代碼的時間都要長。所以在命名的時候想著別讓看你代碼的人痛苦。

關于 print

很嚴肅的說, 不要得到一個 error 或者 response 就在控制臺打印出來。你這么做還不如不打印呢!搞得控制臺一堆亂七八糟的東西看起來真的很爽嗎?

Do:

  • framework 中使用 error 級的 log level
  • 使用一些能夠讓你有不同輸出級別的 log 庫。XGGLoggerSwiftyBeaver
  • 不要用 log 來 debug 了。Xcode 有很多有用的工具Debugging: A Case Study

沒用的代碼

經常在一些老項目里面見到被注釋掉的代碼, 但是出來沒有通過把這些代碼打開來解決過問題。所以, 既然這些代碼都沒有什么用了, 就刪了它! 還能增加代碼的可讀性, 看起來整潔的代碼總要讓人舒服一些。

最后推薦一個好文Using SwiftLint and Danger for Swift Best Practices

原文地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,826評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,728評論 25 708
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,922評論 6 342
  • 前言 人生苦多,快來 Kotlin ,快速學習Kotlin! 什么是Kotlin? Kotlin 是種靜態類型編程...
    任半生囂狂閱讀 26,251評論 9 118
  • 今早上六點起床,補上昨晚沒聽上的Xdite老師的元學習課,收獲如下。 讀書時習慣的學習方法和當前主流的教學方法,很...
    有魚上上簽閱讀 205評論 0 1