本頁包含內容: html
委託(Delegation) ios
錯誤處理(Error Handling) 設計模式
鍵值觀察(Key-Value Observing) 安全
Target-Action模式(Target-Action) 閉包
類型匹配與統一規範(Introspection) app
API 可用性 dom
使用 Cocoa 現有的一些設計模式,是幫助開發者開發一款擁有合理設計思路、穩定的性能、良好的可擴展性應用的有效方法之一。這些模式都依賴於在 Objective-C 中定義的類。由於 Swift 與 Objective-C 的互用性,因此你依然能夠在 Swift 代碼中使用這些設計模式。在一些狀況下,你甚至可使用 Swift 語言的特性擴展或簡化這些 Cocoa 設計模式,使這些設計模式更強大、更易於使用。 ide
委託 函數
在 Swift 和 Objective-C 中,委託一般由一個定義交互方法和遵循規範的委託屬性的協議表示。與 Objective-C 相比,當你在 Swift 中繼承一個委託時,雖然繼承模式不變,可是內部的實現已經改變了。就像在 Objective-C 中,在你向委託發送消息以前,無論它是否是 nil 你都會去查看,若是定義的方法是非必須實現的方法,無論委託有沒有實現這個方法,你也都會去查看。而在 Swift 中,經過保持類型安全的特性,能夠有效的消除這些繁瑣、沒必要要的行爲問題。 性能
下面列出的代碼能夠說明這個過程:
檢查 myDelegate 不爲 nil。
檢查 myDelegate 是否實現了繼承的 window:willUseFullScreenContentSize: 方法。
若是myDelegate 不爲 nil 而且實現了 window:willUseFullScreenContentSize: 方法,那麼調用該方法,將該方法的返回值分配給名爲 fullScreenSize 的屬性。
將該方法的返回值輸出在控制檯。
1
2
3
4
5
6
|
// @inteface MyObject : NSObject
// @property (nonatomic, weak) id delegate;
// @end
if let fullScreenSize = myDelegate?.window?(myWindow, willUseFullScreenContentSize: mySize) {
println(NSStringFromSize(fullScreenSize))
}
|
注意: 在一個徹底使用 Swift 編寫的 app 中,在定義 delegate 屬性時,它做爲一個不定值的 NSWindowDelegate 對象,並將初始值設爲 nil。
錯誤處理
在 Cocoa 中,產生錯誤的方法將NSError指針參數做爲最後一個參數,當錯誤產生時,該參數會被NSError對象填充。Swift 自動的將 Objective-C 中產生錯誤的方法轉換爲 Swift 的原生錯誤處理功能。
注意:產生錯誤的方法,例如代理方法或者採用一個NSError對象做爲參數的完成處理函數,不會被 Swift 處理爲throw的方法。
例如,考慮下面的來自於NSFileManager的 Objective-C 方法:
1
2
|
- (BOOL)removeItemAtURL:(NSURL *)URL
error:(NSError **)error;
|
在 Swift 中,它會被這樣的導入:
1
|
func removeItemAtURL(URL: NSURL) throws
|
注意到removeItemAtURL(_:)方法被 Swift 導入時,返回值類型爲 Void,沒有錯誤參數,而是一個throws聲明。
若是 Objective-C 方法的最後一個非閉包參數是NSError **類型,Swift 則會將之替換爲throws關鍵字,以代表該方法能夠拋出一個錯誤。若是 Objective-C 方法的錯誤參數也是它的第一個參數,Swift 則會嘗試經過刪除選擇器的第一部分中的AndReturnError後綴來進一步簡化方法的名稱,若是存在的話。若是另外一種方法是用所得選擇器聲明的,那麼該方法名將不可改變。
若是產生錯誤的 Objective-C 的方法返回一個用來表示方法調用成功或失敗的BOOL值,Swift 會把函數的返回值轉換爲Void。一樣的,若是產生錯誤的 Objective-C 方法返回一個nil值來代表方法調用的失敗,Swift 會把函數的返回值轉換爲非可選值類型。
不然,若是沒有約定能夠推斷,該方法保持不變。
捕獲和處理錯誤
在 Objective-C 中,錯誤處理是可選的,意味着方法產生的錯誤會被忽略除非你提供了一個錯誤指針。在 Swift 中,調用一個會拋出錯誤的方法要求顯示的進行錯誤處理。
下面是如何在 Objective-C 中處理調用方法產生的錯誤:
1
2
3
4
5
6
7
|
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:@"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error) {
NSLog(@"Error: %@", error.domain);
}
|
下面是 Swift 中等同的代碼:
1
2
3
4
5
6
7
|
let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("/path/to/file")
do {
try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
print("Error: \(error.domain)")
}
|
拋出錯誤
若是一個錯誤出如今了 Objective-C 方法中,那麼該錯誤被用來填充方法的錯誤指針參數。
1
2
3
4
5
6
|
// an error occurred
if (errorPtr) {
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorCannotOpenFile
userInfo:nil];
}
|
若是一個錯誤出如今了 Swift 方法中,那麼該錯誤便會被拋出,而且會自動的傳遞給調用者:
1
2
|
// an error occurred
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
|
若是 Objective-C 代碼調用 Swift 方法拋出了錯誤,那麼該錯誤會被自動的傳遞給橋接的 Objective-C 方法的錯誤指針參數。
例如,考慮NSDocument中的readFromFileWrapper(_:ofType:)方法。在 Objective-C 中,這個方法的最後一個參數是NSError **。當在 Swift 的NSDocument的子類中重寫該方法時,該方法會用throws替代錯誤指針參數。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class SerializedDocument: NSDocument {
static let ErrorDomain = "com.example.error.serialized-document"
var representedObject: [String: AnyObject] = [:]
override func readFromFileWrapper(fileWrapper: NSFileWrapper, ofType typeName: String) throws {
guard let data = fileWrapper.regularFileContents else {
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
}
if case let JSON as [String: AnyObject] = try NSJSONSerialization.JSONObjectWithData(data, options: []) {
self.representedObject = JSON
} else {
throw NSError(domain: SerializedDocument.ErrorDomain, code: -1, userInfo: nil)
}
}
}
|
若是方法不可以使用常規的文件的內容來建立一個對象,則會拋出一個NSError對象。若是方法是從 Swift 代碼中調用的,那麼該錯誤會被傳遞到它的調用域。若是該方法是在 Objective-C 代碼中被調用,錯誤將會傳遞到錯誤指針參數裏。
在 Objective-C 中,錯誤處理是可選的,意味着方法產生的錯誤會被忽略除非你提供了一個錯誤指針。在 Swift 中,調用一個會拋出錯誤的方法要求顯式的進行錯誤處理。
注意:
儘管 Swift 的錯誤處理相似 Objective-C 的異常處理,但它是徹底獨立的功能。若是一個 Objective-C 方法拋出了一個運行時異常,Swift 則會觸發一個運行時錯誤。沒有辦法直接在 Swift 中恢復來自 Objective-C 的異常。任何在 Objective-C 代碼中的異常處理行爲必須用 Swift 來實現。
鍵值觀察
鍵值觀察是一種機制,該機制容許對象得到其餘對象的特定屬性的變化的通知。只要你的類繼承自 NSObject 類,你即可在 Swift 類裏使用鍵值觀察。你能夠在 Swift 中使用下面三步來實現鍵值觀察:
1.爲你想要觀察的屬性添加動態修改符。關於dynamic更多信息,請見Requiring Dynamic Dispatchclass MyObjectToObserve: NSObject {
1
2
3
4
5
|
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}
|
2.建立一個全局上下文變量。
1
|
private var myContext = 0
|
3.爲鍵-路徑增長一個觀察者,重寫observeValueForKeyPath:ofObject:change:context:函數,而且在deinit中移除觀察者。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class MyObserver: NSObject {
var objectToObserve = MyObjectToObserve()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath: "myDate", options: .New, context: &myContext)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [NSObject : AnyObject]?, context: UnsafeMutablePointer) {
if context == &myContext {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("Date changed: \(newValue)")
}
} else {
super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
}
}
deinit {
objectToObserve.removeObserver(self, forKeyPath: "myDate", context: &myContext)
}
}
|
Target-Action模式(Target-Action)
當有特定事件發生,須要一個對象向另外一個對象發送消息時,咱們一般採用 Cocoa 的 Target-Action 設計模式。Swift 和 Objective-C 中的 Target-Action 模型基本相似。在 Swift 中,你可使用 Selector 類型達到 Objective-C 中 selectors 的效果。請在 Objective-C Selectors 中查看在 Swift 中使用 Target-Action 設計模式的示例。
類型匹配與統一規範(Introspection)
在 Objective-C 中,你可使用 isKindOfClass: 方法檢查某個對象是不是指定類型,可使用 conformsToProtocol: 方法檢查某個對象是否遵循特定協議的規範。在 Swift 中,你可使用 is 運算符完成上述的功能,或者也可使用 as? 向下匹配指定類型。
你可使用 is 運算符檢查一個實例是不是指定的子類。若是該實例是指定的子類,那麼 is 運算結果爲 true,反之爲 false。
1
2
3
4
5
|
if object is UIButton {
// object is of type UIButton
} else {
// object is not of type UIButton
}
|
你也可使用 as? 運算符嘗試向下匹配子類型,as? 運算符返回不定值,結合 if-let 語句使用。
1
2
3
4
5
|
if let button = object as? UIButton {
// object is successfully cast to type UIButton and bound to button
} else {
// object could not be cast to type UIButton
}
|
請在 Type Casting 中查看更多信息。
檢查匹配協議的語法與檢查匹配類的語法是同樣的,下面是使用 as? 檢查匹配協議的示例:
1
2
3
4
5
|
if let dataSource = object as? UITableViewDataSource {
// object conforms to UITableViewDataSource and is bound to dataSource
} else {
// object not conform to UITableViewDataSource
}
|
注意,當作完匹配以後,dataSource 會轉換爲 UITableViewDataSource 類型,因此你只能訪問和調用UITableViewDataSource 協議定義的屬性和方法。當你想進行其餘操做時,必須將其轉換爲其餘的類型。
能夠在 Protocols 查看更多相關信息。
API 可用性
一些類和方法並非在你的應用全部平臺的全部版本均可用。爲了確保你的應用功能上可以適應差別,你須要檢查這些 API 的可用性。
在 Objective-C 中,咱們使用respondsToSelector:和instancesRespondToSelector:方法來檢查一個類或者實例方法是否可用。若是沒有檢查,調用方法則會拋出NSInvalidArgumentException「unrecognized selector sent to instance」異常。例如,requestWhenInUseAuthorization方法只在 iOS8.0 和 OS X 10.10 中對CLLocationManager實例可用。
1
2
3
4
5
|
if ([CLLocationManager instancesRespondToSelector:@selector(requestWhenInUseAuthorization)]) {
// 方法可用
} else {
// 方法不可用
}
|
在 Swift 中,嘗試着調用一個目標平臺版本不支持的方法將會報出編譯時錯誤。
下面是上一個例子,採用 Swift 編寫:
1
2
3
|
let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
// error: only available on iOS 8.0 or newer
|
若是應用的目標低於 ios8.0 或者 OSX10.10,requestWhenInUseAuthorization()方法則不可用,因此編譯器會報告錯誤。
Swift 代碼可使用 API 可用性來做爲運行時的條件判斷。可用性檢查可使用在一個控制流語句的條件中,例如if,guard或者while語句。
拿前面的例子舉例,你可使用if語句來檢查可用性,只有當方法在運行時可用時方可調用requestWhenInUseAuthorization()。
1
2
3
4
|
let locationManager = CLLocationManager()
if #available(iOS 8.0, OSX 10.10, *) {
locationManager.requestWhenInUseAuthorization()
}
|
或者,你可使用guard語句來檢查可用性,除非當前的目標符合規定要求,不然將會退出做用域。這種方法簡化了處理不一樣平臺功能的邏輯。
1
2
3
|
let locationManager = CLLocationManager()
guard #available(iOS 8.0, OSX 10.10, *) else { return }
locationManager.requestWhenInUseAuthorization()
|
每一個平臺參數包括下面列出的平臺名稱,後面跟着相應的版本號。最後一個參數是一個星號(*),是用來處理將來潛在的平臺。
平臺名稱:
iOS
iOSApplicationExtension
OSX
OSXApplicationExtension
watchOS
全部的 Cocoa API 都提供有可用性信息,因此你能夠很自信的編寫應用所針對的平臺的代碼。
你能夠經過 @available 屬性來標註聲明從而簡化你的 API 的可用性檢查。@available 屬性使用和 #available 一樣的語法來作運行時檢查,參數都以逗號隔開平臺版本需求。
例如:
1
2
3
4
|
@available(iOS 8.0, OSX 10.10, *)
func useShinyNewFeature() {
// ...
}
|
注意:使用 @available 屬性標記的方法能夠安全的使用知足特定平臺需求的可用 API 而不用顯式的作可用性檢查。