本页包含内容:
委托(Delegation)
错误处理(Error Handling)
键值观察(Key-Value Observing)
Target-Action模式(Target-Action)
类型匹配与统一规范(Introspection)
API 可用性
使用 Cocoa 现有的一些设计模式,是帮助开发者开发一款拥有合理设计思路、稳定的性能、良好的可扩展性应用的有效方法之一。这些模式都依赖于在 Objective-C 中定义的类。因为 Swift 与 Objective-C 的互用性,所以你依然可以在 Swift 代码中使用这些设计模式。在一些情况下,你甚至可以使用 Swift 语言的特性扩展或简化这些 Cocoa 设计模式,使这些设计模式更强大、更易于使用。
委托
在 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 而不用显式的做可用性检查。