《Swifter 100個swift必備的tips》(第二版)閱讀筆記

問題

1)柯里化,通過柯里化,改造target-action,因為selector只能使用字符串,在編譯時無法發現錯誤,并且不利重構。
2)framework和命名空間之間的關系,寫個demo驗證一下。
3)對象初始化的過程,如何解決OC中潛在的初始化bug。
http://stackoverflow.com/questions/8056188/should-i-refer-to-self-property-in-the-init-method-with-arc
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html

4)swift能像OC一樣動態的分派方法嗎? 參見:動態類型和多方法

  1. lazy方法的實現。
    func lazy<S : SequenceType>(s: S) -> LazySequence<S>
    func lazy<S : CollectionType where S.Index : RandomAccessIndexType>(s: S)
    -> LazyRandomAccessCollection<S>
    func lazy<S : CollectionType where S.Index : BidirectionalIndexType>(s: S)
    -> LazyBidirectionalCollection<S>
    func lazy<S : CollectionType where S.Index : ForwardIndexType>(s: S)
    -> LazyForwardCollection<S>”

6) 多重Optional具體在庫中使用的場景是什么?
7)swift中鏈表的時間,enum,indirect
8)實例方法的動態調用測試結果和書上的不一樣
9)一個App的啟動流程是怎樣的?
10)GCD 和延時調用, 這一節的封裝沒看懂。

11)Comparison of Objective-C Enumeration Techniques
https://www.mikeash.com/pyblog/friday-qa-2010-04-09-comparison-of-objective-c-enumeration-techniques.html
12)R.swift 和 SwiftGen管理資源
13)swift的異常機制

1. Swift 新元素

柯里化(currying)

返回一個函數,

柯里化是量產類似方法的好辦法,通過柯里化一個方法模板,可以避免寫出很多重復方法。

Instance Methods are Curried Functions in Swift
https://oleb.net/blog/2014/07/swift-instance-methods-curried-functions/?utm_campaign=iOS_Dev_Weekly_Issue_157&utm_medium=email&utm_source=iOS%252BDev%252BWeekly

將 protocol 的方法聲明為 mutating

寫給別人使用的結構體中需要考慮添加mutating說明,

在使用 class 來實現帶有 mutating 的方法的接口時,具體實現的前面是不需要加 mutating 修飾的,因為 class 可以隨意更改自己的成員變量。所以說在接口里用 mutating 修飾方法,對于 class 的實現是完全透明,可以當作不存在的。

Sequence

具體查看一下Array的sequence實現。

GeneratorType 實現 next()-> Element? 的方法。每次調用都返回下一個元素,不能重置。

SequenceType包含一個Generator的對象。

map, filter, reduce方法是在sequence 基礎上,用方法擴展實現的。它們只要能順序編輯容器類型就可以了。

多元組 (Tuple)

交換值的新寫法

返回多個值

@autoclosure和??

1)閱讀更清晰

2)節省開銷,如果不是閉包,在defaultValue傳值的時候會被求值一次,這個是沒有必要地。

Optional Chaining
optional chaining 返回的一定是一個optional值

操作符

fun的參數修飾

參數是let屬性,不能當做var來使用
如果使用inout,傳參時需要用&的方式。

字面量轉換

特定的類型實現這些協議,將這些字面量轉化為類型的值

下標

方法嵌套

方法現在是一等公民,可以當做參數,返回值,也可以在方法中定義方法。這些子方法只在當前方法內有效。

命名空間

了解framework

Any和Any Object

id和Any object的區別

typeelias和泛型接口

可變參數函數

初始化方法的順序

保證安全的初始化。

Designated,Convenience 和 Required

在 Objective-C 中,init 方法是非常不安全的:沒有人能保證 init 只被調用一次,也沒有人保證在初始化方法調用以后實例的各個變量都完成初始化,甚至如果在初始化里使用屬性進行設置的話,還可能會造成各種問題,雖然 Apple 也明確說明了不應該在 init 中使用屬性來訪問,但是這并不是編譯器強制的,因此還是會有很多開發者犯這樣的錯誤。

所以 Swift 有了超級嚴格的初始化方法。一方面,Swift 強化了 designated 初始化方法的地位。Swift 中不加修飾的 init 方法都需要在方法中保證所有非 Optional 的實例變量被賦值初始化,而在子類中也強制 (顯式或者隱式地) 調用 super 版本的 designated 初始化,所以無論如何走何種路徑,被初始化的對象總是可以完成

因此進行一下總結,可以看到初始化方法永遠遵循以下兩個原則:

  • 初始化路徑必須保證對象完全初始化,這可以通過調用本類型的 designated 初始化方法來得到保證;
  • 子類的 designated 初始化方法必須調用父類的 designated 方法,以保證父類也完成初始化。

初始化返回 nil

所有的結果都將是 Int? 類型,通過 Optional Binding,我們就能知道初始化是否成功,并安全地使用它們了。我們在這類初始化方法中還可以對 self 進行賦值,也算是 init 方法里的特權之一。

protocol 組合

Any的定義 protocol<>
AnyObject的定義?

除了可以方便地表達空接口這一概念以外,protocol 的組合相比于新創建一個接口的最大區別就在于其匿名性。

static 和 class

在類型的定義屬性時,只能使用static,
在定義類型的方法時,enum,struct 只能用static, 類里面可以使用static,也可以使用class

多類型和容器

在容器類使用protocol,這樣就能夠存儲不同的

default 參數

默認參數沒有只能是最后一個參數的限制,因為swift的方法調用可以指定調用時不傳哪些參數

func NSLocalizedString(key: String, tableName: String? = default, bundle: NSBundle = default, value: String = default, comment: String) -> String

func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = default, line: UWord = default)

正則表達式

正則表達式30分鐘入門教程

模式匹配

... 和 ..<

“不難發現,其實這幾個方法都是支持泛型的。除了我們常用的輸入 Int 或者 Double,返回一個 Range 以外,這個操作符還有一個接受 Comparable 的輸入,并返回 ClosedInterval 或 HalfOpenInterval 的重載。在 Swift 中,除了數字以外另一個實現了 Comparable 的基本類型就是 String。也就是說,我們可以通過 ... 或者 ..< 來連接兩個字符串。一個常見的使用場景就是檢查某個字符是否是合法的字符。比如想確認一個單詞里的全部字符都是小寫英文字母的話,可以這么做:”

AnyClass,元類型和 .self

typealias AnyClass = AnyObject.Type

其實在 Swift 中,.self 可以用在類型后面取得類型本身,也可以用在某個實例后面取得這個實例本身。前一種方法可以用來獲得一個表示該類型的值,這在某些時候會很有用;而后者因為拿到的實例本身,所以暫時似乎沒有太多需要這么使用的案例。

self.tableView.registerClass( UITableViewCell.self, forCellReuseIdentifier: "myCell”)

元編程,

通過配置文件初始化

接口和類方法中的 Self

“但是在這種情況下,Self 不僅指代的是實現該接口的類型本身,也包括了這個類型的子類。從概念上來說,Self 十分簡單,但是實際實現一個這樣的方法卻稍微要轉個彎”

動態類型和多方法

Swift 中我們雖然可以通過 dynamicType 來獲取一個對象的動態類型 (也就是運行時的實際類型,而非代碼指定或編譯器看到的類型)。但是在使用中,Swift 現在卻是不支持多方法的,也就是說,不能根據對象在動態時的類型進行合適的重載方法調用。”
(編譯時確定調用哪個方法?)

屬性觀察

初始化方法對屬性的設定,以及在 willSet 和 didSet 中對屬性的再次設定都不會再次觸發屬性觀察的調用,一般來說這會是你所需要的行為,可以放心使用能夠。

屬性觀察 (Property Observers) 是 Swift 中一個很特殊的特性,利用屬性觀察我們可以在當前類型內監視對于屬性的設定,并作出一些響應。Swift 中為我們提供了兩個屬性觀察的方法,它們分別是 willSet 和 didSet。”

final

final 關鍵字可以用在 class,func 或者 var 前面進行修飾,表示不允許對該內容進行繼承或者重寫操作”

“一般來說,不希望被繼承和重寫會有這幾種情況:

類或者方法的功能確實已經完備了”
“子類繼承和修改是一件危險的事情”
“為了父類中某些代碼一定會被執行”

lazy 修飾符和 lazy 方法

lazy屬性的實現, 代碼塊(閉包)的調用 {}(), lazy方法的實現。
func lazy<S : SequenceType>(s: S) -> LazySequence<S> func lazy<S : CollectionType where S.Index : RandomAccessIndexType>(s: S) -> LazyRandomAccessCollection<S> func lazy<S : CollectionType where S.Index : BidirectionalIndexType>(s: S) -> LazyBidirectionalCollection<S> func lazy<S : CollectionType where S.Index : ForwardIndexType>(s: S) -> LazyForwardCollection<S>

Reflection 和 Mirror

swift這個功能還是很弱了,只能讀取值,不能設置值,不能跟OC的運行時相比

隱式解包 Optional

除了在IB中,其它地方慎用。

多重 Optional

具體在庫使用的場景呢?

Optional Map

Protocol Extension

協議擴展里實現了某個方法,類實現里也實現了某個方法。

整理一下相關的規則的話:

如果類型推斷得到的是實際的類型
那么類型中的實現將被調用;如果類型中沒有實現的話,那么接口擴展中的默認實現將被使用
如果類型推斷得到的是接口,而不是實際類型
并且方法在接口中進行了定義,那么類型中的實現將被調用;如果類型中沒有實現,那么接口擴展中的默認實現被使用
否則 (也就是方法沒有在接口中定義),擴展中的默認實現將被調用”

where 和模式匹配

1)應用于switch case, if, for,可以使用if代替
2)應用于泛型限制

對self做進一步限制,如果不遵守Comparable協議,就不能調用sort方法,在編譯時限制
extension SequenceType where Self.Generator.Element : Comparable {
public func sort() -> [Self.Generator.Element]
}

indirect 和嵌套 enum

2. 從 Objective-C/C 到 Swift

Selector

Swift 里對應原來 SEL 的類型是一個叫做 Selector 的結構體。

let someMethod = #selector(callMe) let anotherMethod = #selector(callMeWithParam(_:))

最后需要注意的是,selector 其實是 Objective-C runtime 的概念,如果你的 selector 對應的方法只在 Swift 中可見的話 (也就是說它是一個 Swift 中的 private 方法),在調用這個 selector 時你會遇到一個 unrecognized selector 錯誤,正確的做法是在 private 前面加上 @objc 關鍵字,這樣運行時就能找到對應的方法了。”

實例方法的動態調用

Swift 中可以直接用 Type.instanceMethod 的語法來生成一個可以柯里化的方法。

單例

“在 Swift 1.2 以及之后,如果沒有特別的需求,我們推薦使用下面這樣的方式來寫一個單例:

class MyManager { static let sharedInstance = MyManager() private init() {} }
這種寫法不僅簡潔,而且保證了單例的獨一無二。在初始化類變量的時候,Apple 將會把這個初始化包裝在一次 swift_once_block_invoke 中,以保證它的唯一性。另外,我們在這個類型中加入了一個私有的初始化方法,來覆蓋默認的公開初始化方法,這讓項目中的其他地方不能夠通過 init 來生成自己的 MyManager 實例,也保證了類型單例的唯一性。如果你需要的是類似 defaultManager 的形式的單例 (也就是說這個類的使用者可以創建自己的實例) 的話,可以去掉這個私有的 init 方法。”

條件編譯

swift不支持宏,編譯標記需要在 swift compiler -> Custom Flags 加上 -D 標記

編譯標記
//MARK: //TODO: //FIXME:
暫時還不支持
//WARNING:

@UIApplicationMain

“這個標簽做的事情就是將被標注的類作為委托,去創建一個 UIApplication 并啟動整個程序。在編譯的時候,編譯器將尋找這個標記的類,并自動插入像 main 函數這樣的模板代碼。我們可以試試看把 @UIApplicationMain 去掉會怎么樣:

Undefined symbols _main

說明找不到 main 函數了。

在一般情況下,我們并不需要對這個標簽做任何修改,但是當我們如果想要使用 UIApplication 的子類而不是它本身的話,我們就需要對這部分內容 “做點手腳” 了。

剛才說到,其實 Swift 的 app 也是需要 main 函數的,只不過默認情況下是 @UIApplicationMain 幫助我們自動生成了而已。和 C 系語言的 main.c 或者 main.m 文件一樣,Swift 項目也可以有一個“名為 main.swift 特殊的文件。在這個文件中,我們不需要定義作用域,而可以直接書寫代碼。這個文件中的代碼將作為 main 函數來執行。比如我們在刪除 @UIApplicationMain 后,在項目中添加一個 main.swift 文件,然后加上這樣的代碼:

UIApplicationMain(Process.argc, Process.unsafeArgv, nil,
NSStringFromClass(AppDelegate))
現在編譯運行,就不會再出現錯誤了。”
(監事應用的事件)

@objc 和 dynamic

swift和OC之間的互操作

swift調用OC
通過添加 {product-module-name}-Bridging-Header.h 文件,并在其中填寫想要使用的頭文件名稱,我們就可以很容易地在 Swift 中使用 Objective-C 代碼了

“Objective-C 和 Swift 在底層使用的是兩套完全不同的機制,Cocoa 中的 Objective-C 對象是基于運行時的,它從骨子里遵循了 KVC (Key-Value Coding,通過類似字典的方式存儲對象信息) 以及動態派發 (Dynamic Dispatch,在運行調用時再決定實際調用的具體實現)。而 Swift 為了追求性能,如果沒有特殊需要的話,是不會在運行時再來決定這些的。也就是說,Swift 類型的成員或者方法在編譯時就已經決定,而運行時便不再需要經過一次查找,而可以直接使用。”

“顯而易見,這帶來的問題是如果我們要使用 Objective-C 的代碼或者特性來調用純 Swift 的類型時候,我們會因為找不到所需要的這些運行時信息,而導致失敗。解決起來也很簡單,在 Swift 類型文件中,我們可以將需要暴露給 Objective-C 使用的任何地方 (包括類,屬性和方法等) 的聲明前面加上 @objc 修飾符。注意這個步驟只需要對那些不是繼承自 NSObject 的類型進行,如果你用 Swift 寫的 class 是繼承自 NSObject 的話,Swift 會默認自動為所有的非 private 的類和成員加上 @objc。這就是說,對一個 NSObject 的子類,你只需要導入相應的頭文件就可以在 Objective-C 里使用這個類了。”

“@objc 修飾符的另一個作用是為 Objective-C 側重新聲明方法或者變量的名字。雖然絕大部分時候自動轉換的方法名已經足夠好用 (比如會將 Swift 中類似 init(name: String) 的方法轉換成 -initWithName:(NSString *)name 這樣),但是有時候我們還是期望 Objective-C 里使用和 Swift 中不一樣的方法名或者類的名字,”

可選接口和接口擴展

原生的 Swift protocol 里沒有可選項,所有定義的方法都是必須實現的。如果我們想要像 Objective-C 里那樣定義可選的接口方法,就需要將接口本身定義為 Objective-C 的,也即在 protocol 定義之前加上 @objc。另外和 Objective-C 中的 @optional 不同,我們使用沒有 @ 符號的關鍵字 optional 來定義可選方法:

@objc protocol OptionalProtocol {
optional func optionalMethod()
}

一個不可避免的限制是,使用 @objc 修飾的 protocol 就只能被 class 實現了,也就是說,對于 struct 和 enum 類型,我們是無法令它們所實現的接口中含有可選方法或者屬性的。另外,實現它的 class 中的方法還必須也被標注為 @objc,或者整個類就是繼承自 NSObject。這對我們寫代碼來說是一種很讓人郁悶的限制。

在swift中實現接口可選的方法是通過protocol extension的方式。

內存管理,weak 和 unowned

weak和unowned的區別

如果我們可以確定在整個過程中 self 不會被釋放的話,我們可以將上面的 weak 改為 unowned,這樣就不再需要 strongSelf 的判斷。但是如果在過程中 self 被釋放了而 printName 這個閉包沒有被釋放的話 (比如 生成 Person 后,某個外部變量持有了 printName,隨后這個 Persone 對象被釋放了,但是 printName 已然存在并可能被調用),使用 unowned 將造成崩潰。在這里我們需要根據實際的需求來決定是使用 weak 還是 unowned。

@autoreleasepool

值類型和引用類型

String 還是 NSString

UnsafePointer

C 指針內存管理

COpaquePointer 和 C convention

GCD 和延時調用

獲取對象類型

通過dynamicType方法,進一步使用呢?

自省

如果是繼承自NSObject類,可以用isKindOfClass, isMemberOfClass判斷,
在swift里可以用is判斷,is可以用于枚舉,結構體和類

KVO

只能用于繼承自NSObject的類,
考慮用reactiveCocoa swift版本?(好好研究一下)

局部 scope

C語言用兩個{}來包裹代碼段,swift因為和閉包的沖突,不能直接這樣使用。

用 do
do {
let textLabel = UILabel(frame: CGRectMake(150, 80, 20, 40))
//...
}

匿名閉包

titleLabel = {
let label = UILabel(frame: CGRectMake(150, 30, 20, 40))

return label

}()

判等

swift中 == Equatable接口里的方法。注意的是, func 放在全局,因為要對全局生效。事實上,swfit的操作符都是全局的

protocol Equatable { func ==(lhs: Self, rhs: Self) -> Bool }

class TodoItem { let uuid: String var title: String

init(uuid: String, title: String) { self.uuid = uuid self.title = title } }

extension TodoItem: Equatable { }

func ==(lhs: TodoItem, rhs: TodoItem) -> Bool { return lhs.uuid == rhs.uuid }

對于原來 Objective-C 中使用 == 進行的對象指針的判定,在 Swift 中提供的是另一個操作符 ===。在 Swift 中 === 只有一種重載:

func ===(lhs: AnyObject?, rhs: AnyObject?) -> Bool
它用來判斷兩個 AnyObject 是否是同一個引用。

哈希

protocol Hashable : Equatable { var hashValue: Int { get } }

于哈希值,另一個特別需要提出的是,除非我們正在開發一個哈希散列的數據結構,否則我們不應該直接依賴系統所實現的哈希值來做其他操作。首先哈希的定義是單向的,對于相等的對象或值,我們可以期待它們擁有相同的哈希,但是反過來并不一定成立。其次,某些對象的哈希值有可能隨著系統環境或者時間的變化而改變。因此你也不應該依賴于哈希值來構建一些需要確定對象唯一性的功能,在絕大部分情況下,你將會得到錯誤的結果。

類簇

OC中的實現,
在 Objective-C 中,init 開頭的初始化方法雖然打著初始化的名號,但是實際做的事情和其他方法并沒有太多不同之處。類簇在 Objective-C 中實現起來也很自然,在所謂的“初始化方法”中將 self 進行替換,根據調用的方式或者輸入的類型,返回合適的私有子類對象就可以了。

但是 Swift 中的情況有所不同。因為 Swift 擁有真正的初始化方法,在初始化的時候我們只能得到當前類的實例,并且要完成所有的配置。也就是說對于一個公共類來說,是不可能在初始化方法中返回其子類的信息的。對于 Swift 中 的類簇構建,一種有效的方法是使用工廠方法來進行。例如下面的代碼通過 Drinking 的工廠方法將可樂和啤酒兩個私有類進行了類簇化

使用工程,積累提供一個類方法作為工廠方法。

Swizzle

swift借助OC運行時實現。
SWRoute (了解一下原理)
https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift

調用 C 動態庫

輸出格式化

如果需要制定類似小數點后幾位,借助NSString的format方法來實現。

Options

oc里的NSOptions被轉化為OptionSetType的類型,

public struct UIViewAnimationOptions : OptionSetType {
public init(rawValue: UInt)
static var LayoutSubviews: UIViewAnimationOptions { get }
static var AllowUserInteraction: UIViewAnimationOptions { get }

//...

static var TransitionFlipFromBottom: UIViewAnimationOptions { get }

}

OptionSetType 是實現了 SetAlgebraType 的,因此我們可以對兩個集合進行各種集合運算,包括并集 (union)、交集 (intersect) 等等。另外,對于不需要選項輸入的情況,也就是對應原來的 kNilOptions,現在我們直接使用一個空的集合 [] 來表示:

數組 enumerate

在 Objective-C 中最方便的方式是使用 NSArray 的 enumerateObjectsUsingBlock: 方法

在 Swift 中,我們在遇到這樣的需求的時候,有一個效率,安全性和可讀性都很好的替代,那就是快速枚舉某個數組的 EnumerateGenerator,它的元素是同時包含了元素下標索引以及元素本身的多元組:

var result = 0 for (idx, num) in [1,2,3,4,5].enumerate() { result += num if idx == 2 { break } } print(result)

類型編碼 @encode

C 代碼調用和 @asmname

在導入Darwin的時候,swfit會自動做類型轉換。通過橋接頭文件的方式在swift中引用C函數。

也可以通過asmname來指定名稱。
“//File.swift
//將 C 的 test 方法映射為 Swift 的 c_test 方法
@asmname("test") func c_test(a: Int32) -> Int32

sizeof 和 sizeofValue

//sifeof 3.0 sizeof不能使用了 MemoryLayout<String>.size MemoryLayout.size(ofValue: "abcd")

delegate weak delegate

protocol MyClassDelegate { func method() }

class MyClass { weak var delegate: MyClassDelegate? }

// weak var delegate: MyClassDelegate? 編譯錯誤 // 'weak' cannot be applied to non-class type 'MyClassDelegate”

因為ARC的weak屬性只能用于Class類型,不能用于枚舉和Struct,可以通過使用@objc,或者 protocol MyClassDelegate: class 來實現。推薦使用后者。

Associated Object

swift中也能使用OC運行時這個功能,接口有點不一樣。

func objc_getAssociatedObject(object: AnyObject!, key: UnsafePointer<Void> ) -> AnyObject!

func objc_setAssociatedObject(object: AnyObject!, key: UnsafePointer<Void>, value: AnyObject!, policy: objc_AssociationPolicy)

Lock

@synchronized 雖然這個方法很簡單好用,但是很不幸的是在 Swift 中它已經 (或者是暫時) 不存在了。其實 @synchronized 在幕后做的事情是調用了 objc_sync 中的 objc_sync_enter 和 objc_sync_exit 方法,并且加入了一些異常判斷。因此,在 Swift 中,如果我們忽略掉那些異常的話,我們想要 lock 一個變量的話,可以這樣寫:

func myMethod(anObj: AnyObject!) { objc_sync_enter(anObj) // 在 enter 和 exit 之間 anObj 不會被其他線程改變 objc_sync_exit(anObj) }

更進一步,如果我們喜歡以前的那種形式,甚至可以寫一個全局的方法,并接受一個閉包,來將 objc_sync_enter 和 objc_sync_exit 封裝起來:

func synchronized(lock: AnyObject, closure: () -> ()) { objc_sync_enter(lock) closure() objc_sync_exit(lock) }

Toll-Free Bridging 和 Unmanaged

3. Swift 與開發環境及一些實踐

Swift 命令行工具

可以像腳本語言一樣執行swift

隨機數生成

這是錯誤代碼

let diceFaceCount = 6 let randomRoll = Int(arc4random()) % diceFaceCount + 1 print(randomRoll)

Swift 的 Int 是和 CPU 架構有關的:在 32 位的 CPU 上 (也就是 iPhone 5 和前任們),實際上它是 Int32,而在 64 位 CPU (iPhone 5s 及以后的機型) 上是 Int64。arc4random 所返回的值不論在什么平臺上都是一個 UInt32,于是在 32 位的平臺上就有一半幾率在進行 Int 轉換時越界,時不時的崩潰也就不足為奇了。

這種情況下,一種相對安全的做法是使用一個 arc4random 的改良版本:

func arc4random_uniform(_: UInt32) -> UInt32
這個改良版本接受一個 UInt32 的數字 n 作為輸入,將結果歸一化到 0 到 n - 1 之間。只要我們的輸入不超過 Int 的范圍,就可以避免危險的轉換:

let diceFaceCount: UInt32 = 6 let randomRoll = Int(arc4random_uniform(diceFaceCount)) + 1 print(randomRoll)

最佳實踐當然是為創建一個 Range 的隨機數的方法,這樣我們就能在之后很容易地復用,甚至設計類似與 Randomable 這樣的接口了:

func randomInRange(range: Range<Int>) -> Int { let count = UInt32(range.endIndex - range.startIndex) return Int(arc4random_uniform(count)) + range.startIndex }

for _ in 0...100 { print(randomInRange(1...6)) }

print 和 debugPrint

在extension中實現CustomStringConvertible這個協議

錯誤和異常處理

異常和錯誤的區別
“Swift 現在的異常機制也并不是十全十美的。最大的問題是類型安全,不借助于文檔的話,我們現在是無法從代碼中直接得知所拋出的異常的類型的。”

因此,在 Swift 2 時代中的錯誤處理,現在一般的最佳實踐是對于同步 API 使用異常機制,對于異步 API 使用泛型枚舉。

關于 try 和 throws,想再多講兩個小點。首先,try 可以接 ! 表示強制執行,這代表你確定知道這次調用不會拋出異常。如果在調用中出現了異常的話,你的程序將會崩潰,這和我們在對 Optional 值用 ! 進行強制解包時的行為是一致的。另外,我們也可以在 try 后面加上 ? 來進行嘗試性的運行。try? 會返回一個 Optional 值:如果運行成功,沒有拋出錯誤的話,它會包含這條語句的返回值,否則將為 nil。和其他返回 Optional 的方法類似,一個典型的 try? 的應用場景是和 if let 這樣的語句搭配使用,不過如果你用了 try? 的話,就意味著你無視了錯誤的具體類型:

“值得一提的是,在一個可以 throw 的方法里,我們永遠不應該返回一個 Optional 的值。因為結合 try? 使用的話,這個 Optional 的返回值將被再次包裝一層 Optional,使用這種雙重 Optional 的值非常容易產生錯誤,也十分讓人迷惑 (詳細可參見多重 Optional 的內容)。

斷言

func assert(@autoclosure condition: () -> Bool, @autoclosure _ message: () -> String = default, file: StaticString = default, line: UInt = default)

對應 target 的 Build Settings 中,我們在 Swift Compiler - Custom Flags 中的 Other Swift Flags 中添加 -assert-config Debug 來強制啟用斷言,或者 -assert-config Release 來強制禁用斷言。當然,除非有充足的理由,否則并不建議做這樣的改動。如果我們需要在 Release 發布時在無法繼續時將程序強行終止的話,應該選擇使用 fatalError。

fatalError

在調試時我們可以使用斷言來排除類似這樣的問題,但是斷言只會在 Debug 環境中有效,而在 Release 編譯中所有的斷言都將被禁用。在遇到確實因為輸入的錯誤無法使程序繼續運行的時候,我們一般考慮以產生致命錯誤 (fatalError) 的方式來終止程序。

@noreturn func fatalError(@autoclosure message: () -> String = default, file: StaticString = default, line: UInt = default)
關于語法,唯一要需要解釋的是 @noreturn,這表示調用這個方法的話可以不再需要返回值,因為程序整個都將終止。這可以幫助編譯器進行一些檢查,比如在某些需要返回值的 switch 語句中,我們只希望被 switch 的內容在某些范圍內,那么我們在可以在不屬于這些范圍的 default 塊里直接寫 fatalError 而不再需要指定返回值:

代碼組織和 Framework

但是要特別指出,雖然和 Apple 的框架的后綴名一樣是 .framework,使用方式也類似,但是這些第三方框架都是實實在在的靜態庫,每個 app 需要在編譯的時候進行獨立地鏈接。

framework的制作和引用

安全的資源組織方式

使用字符串指定資源名稱的問題。
OC中可以通過宏來制定文件名稱。

“在 Swift 中是沒有宏定義的,取而代之,我們可以靈活地使用 rawValue 為 String 的 enum 類型來字符串,然后通過為資源類型添加合適的 extension 來讓編譯器幫助我們在資源名稱修改時能在代碼中作為對應的改變。

enum ImageName: String { case MyImage = "my_image" }

enum SegueName: String { case MySegue = "my_segue" }

extension UIImage { convenience init!(imageName: ImageName) { self.init(named: imageName.rawValue) } }

extension UIViewController { func performSegueWithSegueName(segueName: SegueName, sender: AnyObject?) { performSegueWithIdentifier(segueName.rawValue, sender: sender) } }

不過在 Swift 中,根據項目內容來自動化生成像是 ImageName 和 SegueName 這樣的類型并不是一件難事。Swift 社區中現在也有一些比較成熟的自動化工具了,R.swift 和 SwiftGen 就是其中的佼佼者。

Playground 延時運行

為了使 Playground 具有延時運行的本領,我們需要引入 Playground 的 “擴展包” XCPlayground 框架。現在這個框架中包含了幾個與 Playground 的行為交互以及控制 Playground 特性的 API,其中就包括使 Playground 能延時執行的黑魔法,XCPlaygroundPage 和 needsIndefiniteExecution。

我們只需要在剛才的代碼上面加上:

import XCPlayground XCPlaygroundPage.currentPage.needsIndefiniteExecution = true //swift 3.0中,這個屬性已經不推薦使用了

默認時間是30秒, 但是如果你想改變這個時間的話,可以通過 Alt + Cmd + 回車 來打開輔助編輯器。在這里你會看到控制臺輸出和時間軸,將右下角的 30 改成你想要的數字,就可以對延時運行的最長時間進行設定了。

Playground 與項目協作

可以在項目中使用

數學和數字

NAN: Not a Number

JSON

最大的問題在于我們為了保證類型的正確性,做了太多的轉換和判斷。我們并沒有利用一個有效的 JSON 容器總應該是字典或者數組這個有用的特性,而導致每次使用下標取得的值都是需要轉換的 AnyObject。如果我們能夠重載下標的話,就可以通過下標的取值配合 Array 和 Dictionay 的 Optional Binding 來簡單地在 JSON 中取值。鑒于篇幅,我們在這里不給出具體的實現。感興趣的讀者可以移步看看 json-swift 或者 SwiftyJSON 這樣的項目,它就使用了重載下標訪問的方式簡化了 JSON 操作。使用這個工具,上面的訪問可以簡化為下面的類型安全的樣子:

// 使用 SwiftJSON
if let value = JSON(json)["menu"]["popup"]["menuitem"][0]["value"].string {
print(value)
}

這樣就簡單多了。

NSNull

文檔注釋

性能考慮

Log 輸出

func printLog<T>(message: T,
                    file: String = #file,
                  method: String = #function,
                    line: Int = #line)
{
  #if DEBUG
    print("\((file as NSString).lastPathComponent)[\(line)], \(method): \(message)")
   #endif
}

新版本的 LLVM 編譯器在遇到這個空方法時,甚至會直接將這個方法整個去掉,完全不去調用它,從而實現零成本。

溢出

如果我們想要其他編程語言那樣的對溢出處理溫柔一些,不是讓程序崩潰,而是簡單地從高位截斷的話,可以使用溢出處理的運算符,在 Swift 中,我們可以使用以下這五個帶有 & 的操作符,這樣 Swift 就會忽略掉溢出的錯誤:

溢出加法 (&+)
溢出減法 (&-)
溢出乘法 (&*)
溢出除法 (&/)
溢出求模 (&%)

宏定義 define

屬性訪問控制

public class MyClass {
public private(set) var name: String?
}

Swift 中的測試

@testable

Core Data

閉包歧義

“為了增強可讀性和安全性,最直接是在調用時盡量指明閉包參數的類型。

泛型擴展

我們不能通過擴展來重新定義當前已有的泛型符號,但是可以對其進行使用;在擴展中也不能為這個類型添加泛型符號;但只要名字不沖突,我們是可以在新聲明的方法中定義和使用新的泛型符號的。

兼容性

Apple 通過將一個最小化的運行庫集成打包到 app 中這樣的方式來解決兼容性的問題。使用了 Swift 語言的項目在編譯時會在 app 包中帶有這一套運行時環境,并在啟動時加載這些 dylib 包作為 Swift 代碼的運行環境。這些庫文件位于打包好的 app 的 Frameworks 文件夾中:
這樣帶來的好處有兩點。首先是雖然 Swift 語言在不斷變化,但是你的 app 不論在什么樣的系統版本上都可以保持與開發編譯時的行為一致,因為你依賴的 Swift 運行時是和 app 綁定的。這對于確保 Swift 升級后新版本的 app 在原有的設備和系統上運行正常是必要的。
另一個好處是向下兼容。雖然 Swift 是和 iOS 8 及 OSX 10.10 一同推出的,但是通過加載 Swift 的動態庫,Apple 允許 Swift 開發的 app 在 iOS 7 和 OSX 10.9 上也能運行,這對 Swift 的盡快推廣和使用也是十分關鍵的。

列舉 enum 類型

(不太懂)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容