混用Swift和objc - 使用Cocoa

能够在 objc 和 Swift 之间相互转换的类型称作bridged类型,比如 Swift 中的String可以作为NSString传给 objc。

Foundation

Foundation 框架为程序提供了基础的功能。

Bridged 类型

详见表格

这些类型和它们对应的类型功能一样,在 Swift 中,可变性用varlet控制,所以不用两种类型。objc 的引用类型与它们所对应的 Swift 的值类型基本上相差NS前缀。

在 Swift 与 objc 相互导入时,导入器会替换相关的 bridged 类型。

Renamed 类型

Swift 的 Foundation 重命名了一些类和协议,包括相关的枚举类型和常量。

一般,类都去掉了NS前缀,也有一些特例:

  • objc 特有的类或与 objc runtime 相关的类:NSObject, NSAutoreleasePool, NSException, NSProxy
  • 平台特有的类:NSBackgroundActivity, NSUserNotification, NSXPCConnection
  • 有等价值类型的类,如 Bridged 类型所述:NSString, NSDictionary, NSURL
  • 没有等价类型的类,但在不久的将来会有的:NSAttributedString, NSRegularExpression, NSPredicate

Foundation 框架中有很多枚举和常量,当 Swift 导入这些类型时,会把它们作为相关类的嵌套类型。如NSJSONReadingOptions会导入为JSONSerialization.ReadingOptions

Strings

在 Swift 中要创建NSString,可以用as转型String,也可以用字符串常量来创建。

1
2
3
4
5
6
7
8
9
import Foundation
let string: String = "abc"
let bridgedString: NSString = string as NSString
let stringLiteral: NSString = "123"
if let integerValue = Int(stringLiteral as String) {
print("\(stringLiteral) is the integer \(integerValue)")
}
// Prints "123 is the integer 123"

Numbers

NSNumber可以桥接 Swift 的数值类型,包括Int, Double, Bool

把数值类型转型为NSNumber时,要用as?操作符。

1
2
3
4
5
6
7
import Foundation
let number = 42
let bridgedNumber: NSNumber = number as NSNumber
let integerLiteral: NSNumber = 5
let floatLiteral: NSNumber = 3.14159
let booleanLiteral: NSNumber = true

objc 平台相关的整形类型,如NSUIntegerNSInteger,都与Int对应。

Arrays

当桥接一个参数化的NSArray对象时,其中的元素也会被桥接。如果没有参数化,就会桥接为类型[Any]的数组。

1
2
3
4
@property NSArray *objects;
@property NSArray<NSDate *> *dates;
- (NSArray<NSDate *> *)datesBeforeDate:(NSDate *)date;
- (void)addDatesParsedFromTimestamps:(NSArray<NSString *> *)timestamps;
1
2
3
4
var objects: [Any]
var dates: [Date]
func datesBeforeDate(date: Date) -> [Date] {}
func addDatesParsedFromTimestamps(timestamps: [String]) {}

可以直接用字面值定义一个NSArray对象:

1
2
let schoolSupplies: NSArray = ["Pencil", "Eraser", "Notebook"]
// schoolSupplies is an NSArray object containing three values

Sets

集合的桥接与数组类似,如果NSSet没有明确参数化类型,就会桥接为Set<AnyHashable>

1
2
3
4
@property NSSet *objects;
@property NSSet<NSString *> *words;
- (NSSet<NSString *> *)wordsMatchingPredicate:(NSPredicate *)predicate;
- (void)removeWords:(NSSet<NSString *> *)words;
1
2
3
4
var objects: Set<AnyHashable>
var words: Set<String>
func wordsMatchingPredicate(predicate: NSPredicate) -> Set<String> {}
func removeWords(words: Set<String>) {}
1
2
let amenities: NSSet = ["Sauna", "Steam Room", "Jacuzzi"]
// amenities is an NSSet object containing three values

Dictionaries

为定义参数化类型的NSDictionary桥接为[AnyHashable: Any]

1
2
3
4
@property NSDictionary *keyedObjects;
@property NSDictionary<NSURL *, NSData *> *cachedData;
- (NSDictionary<NSURL *, NSNumber *> *)fileSizesForURLsWithSuffix:(NSString *)suffix;
- (void)setCacheExpirations:(NSDictionary<NSURL *, NSDate *> *)expirations;
1
2
3
4
var keyedObjects: [AnyHashable: Any]
var cachedData: [URL: Data]
func fileSizesForURLsWithSuffix(suffix: String) -> [URL: NSNumber] {}
func setCacheExpirations(expirations: [URL: NSDate]) {}
1
2
let medalRankings: NSDictionary = ["Gold": "1st Place", "Silver": "2nd Place", "Bronze": "3rd Place"]
// medalRankings is an NSDictionary object containing three key-value pairs

Core Foundation

CF 类型会被导入为 Swift 类。还会提供内存管理注解,这样 Swift 就能自动管理 CF 对象的内存,包括你自己实例化的 CF 对象。In Swift, you can use each pair of toll-free bridged Foundation and Core Foundation types interchangeably. You can also bridge some toll-free bridged Core Foundation types to Swift standard library types if you cast to a bridging Foundation type first.

Remapped Types

Swift 导入 CF 类型时,编译器会重新映射这些类型的名字。从每个类型名最后移除Ref,因为所有 Swift 类都是引用类型。

CFTypeRed类型会重新映射为AnyObject类型。

Memory Managed Objects

从注解过的API中返回的 CF 对象会被 Swift 自动管理内存,不需要自己调用CFRetain, CFRelease, CFAutorelease

如果你想要在你自己的 C 方法或 objc 方法中返回 CF 对象,可以用CF_RETURNS_RETAINEDCF_RETURNS_NOT_RETAINED宏来注解,就能自动插入内存管理语句。也可以用CF_IMPLICIT_BRIDGING_ENABLEDCF_IMPLICIT_BRIDGING_DISABLED宏,来包裹 C 方法声明,遵循 CF 所有权策略和命名策略,从而根据命名推断出内存管理。

Unmanaged Objects

当 Swift 导入的 API 没有注解,就不能自动管理返回的 CF 对象的内存。Swift 会将返回的 CF 对象包裹在Unmanaged<Instance>结构中。所有间接返回的 CF 对象也是 unmanaged。如下是未注解的 C 方法:

1
CFStringRef StringByAddingTwoStrings(CFStringRef s1, CFStringRef s2)

Swift 这么导入:

1
2
3
func StringByAddingTwoStrings(_: CFString!, _: CFString!) -> Unmanaged<CFString>! {
// ...
}

当你从一个未注解的 API 收到一个非托管的对象时,要马上把它转化成一个内存管理对象。Unmanaged<Instance>结构体提供两个方法来转化成内存管理对象——takeUnretainedValue()takeRetainedValue()。这两个方法都返回原始的、解包的对象类型。

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

当然也可以用retain()release()autorelease()方法来管理非托管对象,但这些方法不推荐。

Unified Logging

标准日志系统提供 API 来替代NSLog方法。

Swift 中,可以用顶级的os_log(_:dso:log:type:_:)方法与标准日志系统交互,在模块 os 的子模块 log 中。

1
2
3
import os.log
os_log("This is a log message.")

NSStringprintf格式的字符串来格式化日志消息。

1
2
let fileSize = 1234567890
os_log("Finished downloading file. Size: %{iec-bytes}d", fileSize)

也可以明确日志的层级,如 Info、Debug、Error。

1
os_log("This is additional info that may be helpful for troubleshooting.", type: .info)

要对特定的子系统记录日志,可以创建OSLog对象,明确子系统和分类,作为参数传给os_log方法。

1
2
let customLog = OSLog("com.your_company.your_subsystem_name.plist", "your_category_name")
os_log("This is info that may be helpful during development or debugging.", log: customLog, type: .debug)

Cocoa Structures

把 Swift 代码转换回 objc 代码时,Cocoa 和 Foundation 中内置的结构体会转换为NSValue实例。As a result, you can use an Objective-C structure from Swift in Cocoa APIs that accept only instances of reference types. This is true even though the instance’s defining type is bridged to Swift as a structure type.

下面的结构体会桥接为NSValue

  • CATransform3D
  • CLLocationCoordinate2D
  • CGAffineTransform
  • CGPoint
  • CGRect
  • CGSize
  • CGVector
  • CMTimeMapping
  • CMTimeRange
  • CMTime
  • MKCoordinateSpan
  • NSRange
  • SCNMatrix4
  • SCNVector3
  • SCNVector4
  • UIEdgeInsets
  • UIOffset

参考