使用Swift与Cocoa和Objective-C交互-(4)使用 Cocoa 数据类型

swift 发布于 2017年09月23日
Strings

Swift 自动对 StringNSString 类进行桥接。这意味着任何你使用 NSString 的地方,你都可以使用 Swift 的 String 类型来替代并且能够获得这两种类型的所有特性——String 类型的插值和为 Swift 设计的 API,还有 NSString 类广泛的功能。由于这个原因,你应该几乎不需要在你的代码中直接使用 NSString。事实上,当 Swift 导入 Objective-C API 的时候,它将所有的 NSString 类型替换成 String 类型。当你的 Objective-C 代码使用 Swift 类的时候,导入器将所有的 String 类型替换成 NSString 类型。

要打开 String 桥接,只需要引入 Foundation 库。例如,你可以在 Swift 字符串中调用 NSString 类的 capitalizedString 方法,并且 Swift 会自动将 Swift 的 String 类桥接到 NSString 对象并调用这个方法。这个方法甚至还可以返回 Swift 类型,因为他在导入过程中被转换了。

import Foundation
let greeting = "hello, world!"
let capitalizedGreeting = greeting.capitalizedString
// capitalizedGreeting: String = Hello, World!

如果你需要使用 NSString 对象,你可以通过类型转换将它转换为 Swift 的 String 类型。String 类型总能够从NSString 转换到 Swift 的 String,所以你不需要使用 as? 的 Optional 版本来进行转化。你还可以通过直接输入常量和变量来从字符串字面值中创建一个 NSString 对象。

import Foundation
let myString: NSString = "123"
if let integerValue = (myString as String).toInt() {
    println("\(myString) is the integer \(integerValue)")
}
本地化

在 Objective-C 中,你使用 NSLocalizedString 宏系列来对字符串进行本地化,这组宏包括 NSLocalizedStringNSLocalizedStringFromTableNSLocalizedStringFromTableInBundle,和NSLocalizedStringWithDefaultValue。在 Swift 中你可以使用单独一个函数来完成和上面那些同样的功能——NSLocalizedString(key:tableName:bundle:value:comment:)NSLocalizedString 函数提供了tableNamebundlevalue 参数的默认值。

Numbers

Swift 自动地桥接原生的 number 类型,比如 IntFloatNSNumber。这样的桥接让你从这些类型中创建 NSNumber

let n = 42
let m: NSNumber = n

它还允许你传入 Int 值。例如,对于那些 NSNumber 类型的参数。然而,因为 NSNumber 可以包含一些列不同的类型,你不能将他们传递给那些需要 Int 类型的值。

所有下面这些类型都自动桥接到 NSNumber

  • Int
  • UInt
  • Float
  • Double
  • Bool
集合类

Swift 自动将 NSArrayNSDictionary 类桥接到他们的原生 Swift 实现中。这意味着你可以使用 Swift 强大的算法和自然的语法来操作集合——并可以互换的使用 Foundation 和 Swift 集合类型。

数组

Swift 自动对 ArrayNSArray 进行桥接。当你将 NSArray 桥接到 Swift 数组中时,最终得到的数组是 [AnyObject] 类型。Objective-CSwift 类都是和 AnyObject 兼容的,或者对象可以桥接到其中一个。因为 Objective-C 对象是和 AnyObject 兼容的,所以你可以将任何的 NSArray 对象桥接到 Swift 数组中。因为所有的 NSArray 对象都可以桥接到 Swift 数组中,Swift 编译器在导入 Objective-C API的时候,将所有的 NSArray 类替换成 [AnyObject]

在你将 NSArray 对象桥接到 Swift 数组中后,你还可以将数组 向下转型 到更具体的类型。与将 NSArray 转换成 [NSArray] 类型不一样,从 AnyObject 转换到具体的类型不保证成功。编译器知道运行时才能确定知道数组中所有的元素是否能够转换为你指定的类型。所以,从 [AnyObject] 向下转型到 [SomeType] 会返回一个 Optional 值。例如,如果你知道 Swift 数组仅包含 UIView 类的实例(或 UIView 的子类),你可以将数组中的 AnyObject 类型元素转换成 UIView 对象。如果 Swift 数组中的任意元素在运行时不是 UIView 类型,这个转换会返回 nil

let swiftArray = foundationArray as [AnyObject]
if let downcastedSwiftArray = swiftArray as? [UIView] {
    // downcastedSwiftArray contains only UIView objects
}

你可以在一个 for 循环中,直接从 NSArray 对象向下转型到包含指定类型的数组:

for aView: UIView! in foundationArray {
    // aView is of type UIView
}

另外,你可以对你正在遍历的数组进行向下转型——这两种风格的结果都是一样的:

for aView in foundationArray as [UIView] {
    // aView is of type UIView
}

这种转换是强制转换,如果转换不成功,将会产生运行时错误。

当你将 Swift 数组转换成 NSArary 对象的时候,Swift 数组中得元素必须是与 AnyObject 兼容的。例如,一个包含 Int 类型元素的 [Int] 类型的数组。Int 类型不是类的一个实例,但因为 Int 类型和 NSNumber 可以桥接,这个 Int 类型是和 Anyobject 兼容的。这样你可以将 [Int] 类型的 Swift 数组桥接到 NSArray 对象上面。如果 Swift 数组中的一个元素不是 AnyObject 兼容的,当你桥接到 NSArray 对象的时候,就会产生一个运行时错误。

你还可以从 Swift 字面值中直接创建 NSArray 对象,就像下面代码中描述的那样。当你明确的将常量或变量指定为 NSArray 类型的对象并给他赋一个数组字面值,Swift 会创建一个 NSArray 对象而不是 Swift 数组。

let schoolSupplies: NSArray = ["Pencil", "Eraser", "Notebook"]
// schoolSupplies is an NSArray object containing NSString objects

在上面的例子中,Swift 数组字面值包含了 String 字面值。因为 String 类型桥接到 NSString 类中,数组字面值被桥接到 NSArray 对象并赋值到 schoolSupplies 对象中。

当你在 Objective-C 代码中使用 Swift 类或协议的时候,导入器将它导入到所有 Swift 数组转换成 NSArray。如果你将 NSArray 传递给需要不同类型参数的 Swift 方法,会产生一个运行时错误。如果 Swift API 返回的 Swift 数组不能被桥接到 NSArray,会产生一个运行时错误。

字典

除了数组,Swift 还自动对 DictionaryNSDictionary 类型之间进行自动转换。当你将 NSDictionary 类桥接到 Swift 字典中,返回的字典类型是 [NSObject: AnyObject]。你可以将任意的 NSDictionary 对象桥接到 Swift 字典上,因为所有的 Objective-C 对象都是与 AnyObject 兼容的。回忆一下,一个 Objective-C 或 Swift 类的实例是和 AnyObject 兼容的,或者它可以桥接到其中一个。所有的 NSDictionary 对象都可以桥接到 Swift 字典中,这样 Swift 编译器当在导入 Objective-C API 的时候,会将 NSDictionary 类替换成 [NSObject: AnyObject]。比如,当你在 Objective-C 代码中使用 Swift 类或协议,导入器将会把与 Objective-C 兼容的 Swift 字典,重新映射成 NSDictionary 对象。

在你将 NSDictionary 对象桥接到 Swift 字典后,你还可以将这个字典向下转型到更具体的类型。就像向下转型 Swift 数组,向下转型 Swift 字典不保证成功。将 [NSObject: AnyObject]进行向下转到更具体类型的返回结果是一个 Optional 值。

当你以反方向进行转型的时候——从 Swift 字典转换到 NSDictionary 对象——键和值必须是类的一个实例,或可桥接到一个类的实例。

你还可以直接从 Swift 字典字面值中创建一个 NSDictionary 对象,依照上述的桥接规则。当你明确地创建一个 NSDictionary 常量或变量,并把他们赋值给一个字典字面值的时候,Swift 会创建一个 NSDictionary 对象,而不是 Swift 字典。

Foundation 数据类型

Swift 提供一个方便的接口用于覆盖定义在 Foundation 框架中的数据类型。使用这个覆盖用于类似 NSSizeNSPoint 这样的数据类型,通过和 Swift 语言通用的语法。例如,你可以用这样的语法来创建 NSSize 结构:

let size = NSSize(width: 20, height: 40)

这个覆盖还能让你用自然的方式调用在结构体上的 Foundation 函数。

let rect = CGRect(x: 50, y: 50, width: 100, height: 100)
let width = rect.width    // equivalent of CGRectGetWidth(rect)
let maxX = rect.maxY      // equivalent of CGRectGetMaxY(rect)

Swift 将 NSUIntegerNSInteger 桥接到 Int。这两个类型在 Foundation API 中,都以 Int 形式存在。 Int 在 Swift 用作一致性,但如果你需要无符号整形数据的话,也可以使用 UInt

Foundation 函数

NSLog 可以用于在系统控制台中输出日志。使用和 Objective-C 中同样的语法。

NSLog("%.7f", pi)         // Logs "3.1415927" to the console

然而,Swift 还有像是 printprintln 这样的函数。这些函数因为 Swift 的字符串插值变得简单强大。他们不会打印到控制台中,但可以用作通用的打印需要。

NSAssert 函数没有转移到 Swift 中,你需要使用 assert 函数。

Core Foundation

Core Foundation 类型会自动导入到 Swift 类中。无论内存管理标记是否已经提供, Swift 自动管理 Core Foundation 对象的内存,包括你自己实例化的 Core Foundation 对象。在 Swift 中,你可以交替使用两个方向的 Foundation 和 Core Foundation 类型。你还可以将一些自动桥接的 Core Foundation 类型桥接到 Swift 标准库类型。

重新映射类型

当 Swift 导入 Core Foundation 类型,编译器重新映射这些类型。编译器会删除每个类型名称后面的 Ref,因为所有 Swift 类都是引用类型,这样的后缀就多余了。

Core Foundation 的 CFTypeRef 完全映射到 AnyObject 类型。在你用到 CFTypeRef 的任何地方,你应该在你的代码中使用 AnyObject

内存管理对象

Core Foundation 返回的被标记的 API 在 Swift 中会进行自动的内存管理 —— 你不需要自己调用 CFRetainCFRelease,或 CFAutorelease。如果你从自己的 C 函数和 Objective-C 方法中返回 Core Foundation 对象,将他们标记为 CF_RETURNS_RETAINEDCF_RETURNS_NOT_RETAINED。编译器在编译 Swift 代码时会自动为他们加上内存管理相关的调用。如果你只是使用标记 API,没有简介返回 Core Foundation 对象,你可以跳过后面的章节。反之,继续去学习关于操作未被管理的 Core Foundation 对象。

未被管理的对象

当 Swift 导入没有被标记的 API 时,编译器就不能对返回的 Core Foundation 对象进行自动内存管理。Swift 将这些返回的 Core Foundation 对象封装进 UNmanaged<T> 结构中。虽有间接返回的 Core Foundation 对象也都是未被管理的。例如,这里是未被标记的 C 函数:

CFStringRef StringByAddingTwoStrings(CFStringRef string1, CFStringRef string2)

这里是如何导入到 Swift 中:

func StringByAddingTwoStrings(CFString!, CFString!) -> Unmanaged<CFString>!

当你从未标记的 API 中接受到一个未被管理的对象时,你应该在使用它之前把他转换成被内存管理的对象。这样,Swift 就可以帮你处理内存管理的问题。Unmanaged<T> 结构提供了两个方法将未管理的对象转换成内存管理的对象——takeUnretainedValue()takeRetainedValue()。这两个方法都返回原始并且未封装类型的对象。你通过你调用的 API 是否返回 retain 的对象来选择调用其中哪个方法。

例如,假如上面的 C 函数在返回 CFString 之前没有进行 retain 操作。要开始使用这个对象,你要使用 takeUnretainedValue() 函数。

let memoryManagedResult = StringByAddingTwoStrings(str1, str2).takeUnretainedValue()
// memoryManagedResult is a memory managed CFString

你还可以调用 retain(),release()autorelease() 方法,但这个方式是不推荐的。


如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

本站文章均为原创内容,如需转载请注明出处,谢谢。
关注微信公众号
发现更多精彩
swift-cafe