文档章节

Swift互用性:采用Cocoa设计模式(Swift 2.0版)

法斗斗
 法斗斗
发布于 2016/03/01 18:41
字数 2998
阅读 14
收藏 0
点赞 1
评论 0

Swift互用性:采用Cocoa设计模式(Swift 2.0版)

Using Swift with Cocoa and Objective-C:采用Cocoa设计模式(Swift 2.0版更新)

本页包含内容:

  • 委托(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 中,通过保持类型安全的特性,可以有效的消除这些繁琐、不必要的行为问题。

下面列出的代码可以说明这个过程:

  1. 检查 myDelegate 不为 nil。

  2. 检查 myDelegate 是否实现了继承的 window:willUseFullScreenContentSize: 方法。

  3. 如果myDelegate 不为 nil 并且实现了 window:willUseFullScreenContentSize: 方法,那么调用该方法,将该方法的返回值分配给名为 fullScreenSize 的属性。

  4. 将该方法的返回值输出在控制台。

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 而不用显式的做可用性检查。


本文转载自:

共有 人打赏支持
法斗斗
粉丝 20
博文 367
码字总数 17774
作品 0
杨浦
程序员
设计模式(Swift) - 3.观察者模式、建造者模式

上一篇 设计模式(Swift) - 2.单例模式、备忘录模式和策略模式中讲了三种常见的设计模式. 单例模式: 限制了类的实例化,一个类只能实例化一个对象,所有对单例对象的引用都是指向了同一个对象....

Dariel
07/01
0
0
Objective-C中单例模式的实现

单例模式在Cocoa和Cocoa Touch中非常常见。比如这两个,[UIApplication sharedApplication]和[NSApplication sharedApplication],大家应该都见过。但是我们应该如何在代码中实现一个单例模式...

雾灵峰
2013/03/24
0
0
23种设计模式(3):抽象工厂模式

定义:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。 类型:创建类模式 类图: 抽象工厂模式与工厂方法模式的区别 抽象工厂模式是工厂方法模式的升级版本,他用来...

LCZ777
2014/07/05
0
0
设计模式-抽象工厂模式(C#)

设计模式——抽象工厂模式(JAVA) 在抽象工厂模式中,一个具体工厂可以生产一组相关的具体产品,这样的一组产品成为产品族,产品族中的每一个产品都属于某一个产品继承等等级结构。当系统所...

PXZ6603
06/26
0
0
简单工厂和工厂方法以及抽象工厂模式

工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类 抽象工厂模式:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类 个人觉得这个区别在于产品,如果产...

王大叔爱编程
2014/09/26
0
0
设计模式之禅(第2版).epub

【下载地址】 本书是设计模式领域公认的3本经典著作之一,“极具趣味,容易理解,但讲解又极为严谨和透彻”是本书的写作风格和方法的最大特点。第1版2010年出版,畅销至今,广受好评,是该领...

winter730
05/16
0
0
代理模式(Proxy Pattern):动态代理 - 最易懂的设计模式解析

前言 今天我来全面总结开发中最常用的设计模式 - 代理模式中的动态代理模式 其他设计模式介绍 1分钟全面了解“设计模式” 单例模式(Singleton) - 最易懂的设计模式解析 简单工厂模式(Sim...

Carson_Ho
04/09
0
0
手把手教学:详解Swift中的iOS设计模式

说到设计模式,相信大家都不陌生,但是又有多少人知道它背后的真正含义?绝大多数程序员都知道设计模式十分重要,不过关于这个话题的文章却不是很多,开发者们在开发的时候有时也不太在意设计...

hejunbinlan
2015/08/22
0
0
系统架构技能之设计模式-单件模式

一、开篇 其实我本来不是打算把系统架构中的一些设计模式单独抽出来讲解的,因为很多的好朋友也比较关注这方面的内容,所以我想通过我理解及平时项目中应用到的一 些常见的设计模式,拿出来给...

wbf961127
2017/11/12
0
0
JavaScript设计模式系列三之单例模式(附案例源码)

文章初衷 设计模式其实旨在解决语言本身存在的缺陷 目前javaScript一些新的语法特性已经集成了一些设计模式的实现, 大家在写代码的时候,没必要为了用设计模式而去用设计模式, 那么我这边为什...

小钱钱阿圣
2017/09/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Python -re模块及正则表达式解析

传送门: https://blog.csdn.net/pipisorry/article/details/25909899 ps:上面文章中"命名分组"的语法格式不能执行。正确的如下: (?P<name>正则表达式) #name是一个合法的标识符 除了使用别名...

一口今心
10分钟前
0
0
mybatis中session.getMapper方法源码分析

0开始代码AuthorMapper mapper = session.getMapper(AuthorMapper.class); 1 DefaultSqlSession类 @Override public <T> T getMapper(Class<T> type) { //最后会去调用MapperRegistry.getMap......

writeademo
19分钟前
0
0
spring cloud zuul网关的作用

zuul一般有两大作用,1是类似于Nginx的网址重定向,但zuul的重定向的一般是整个spring cloud里在Eureka注册中心的模块. zuul: ignored-services: '*' sensitiveHeaders: routes: ...

算法之名
19分钟前
8
0
java按比例之原图生成缩略图

package com.wxp.test; import java.awt.Image; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; import javax.imageio.ImageIO; import sun.......

恋码之子
29分钟前
1
0
SpringCloud 微服务 (十五) 服务容错 Hystrix

壹 工作中的微服务架构,某个服务通常会被多个服务调用或者多层调用完成需求,如果某个服务不可用,导致一个系统功能不可用或者服务直接没用了的情况,这种情况称为雪崩效应 有A服务调用B服务,B服...

___大侠
31分钟前
0
0
Spring框架中的设计模式(五)

Spring框架中的设计模式(五) 通过以前的4篇文章,我们看到Spring采用了大量的关于创建和结构方面的设计模式。本文将描述属于行为方面的两种设计模式:命令和访问者。 前传: Spring框架中的...

瑞查德-Jack
33分钟前
0
0
解决phpstorm运行很卡问题!

phpStorm一旦达到这个临界值,所有智能提示、自动补全都失效了 这TM就很尴尬了,顿时感觉自己就是个废人了,纯手写代码跟便秘一样 众所周知phpStorm基于JAVA,那么这个内存限制肯定跟JAVA的虚...

sjcehui2010
36分钟前
0
0
javascript前端AES加密解密

参考了一下网上的代码加上自已的一些想法,修改,key也可以是中文, 要引入一个aes.js的js文件。 html代码 <html> <head> <title>AES加解密</title> <meta http-equiv="Content-Type"......

oisan_
40分钟前
0
0
MacOS和Linux内核的区别

有些人可能认为MacOS和Linux内核有相似之处,因为它们可以处理类似的命令和类似的软件。甚至有人认为苹果的MacOS是基于linux的。事实上,这两个内核的历史和特性是非常不同的。今天,我们来看...

六库科技
44分钟前
0
0
Vue.js-自定义事件例子

自定义组件的 v-model 2.2.0+ 新增 一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。m...

tianyawhl
47分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部