Swift
与Objective-C
之间具备双向的互操作性,在一种语言中可以使用另一种语言写的代码。目前,在用 Swift 写新项目时,可能会调用以前 objc 写的代码,这是一个重要的方面。苹果很好的做到了这一点,在用原生 Swift 写代码时,可以通过导入 objc 文件,就可以初始化 objc 对象和调用方法了。
初始化
objc 的初始化方法都以init
开头,with
后面的参数都会放在 Swift 方法后的括号中。Swift 中初始化的方法名都为init
,而后面括号中的参数可以不同:
|
|
上面的 objc 代码转化为 Swift 代码:
|
|
在调用初始化方法时:
|
|
在 Swift 中则是这样:
|
|
类工厂方法和快捷初始化器
在 objc,可以这样用类工厂方法:
|
|
Swift:
|
|
(在 Swift 中,类方法跟初始化方法调用起来没啥不同。。)
可失败的初始化
在 objc 中,如果初始化失败,会返回nil
。在 Swift 中,这种特性叫做failable initialization
。objc 中,可以通过nullability annotations
反应初始化是否会失败,但这不是强制的,详见可空和可选。Swift 中,不会失败的初始化方法定义为init(...)
,而可能失败的初始化方法定义为init?(...)
。另外,objc 的初始化方法会转换成init!(...)
。
比如,一个UIImage
对象可能因为图片地址错误而初始化失败:
|
|
访问属性
objc 中的属性会以以下方式转化:
- 可空的属性(
nonnull
,nullable
,null_resettable
)会以可选或非可选类型导入,详见可空和可选。 - 只读属性导入为带
getter
的属性 weak
属性转化为 Swift 属性后,用weak
标记(weak var
)- 其他内存管理修饰符(
assign
,copy
,strong
,unsafe_unretained
)转化为正确的存储方式 class
性质的属性导入为 Swift 的类型属性- 原子性(
atomic
,nonatomic
)不会显示在 Swift 的属性声明中,但从 Swift 中访问 objc 属性时,objc 定义的原子性将会保持。 - 存取方法名(
getter=
,setter=
)会被 Swift 忽略
Swift 访问属性的方式:
|
|
objc 中,无参数有返回值的方法可以用.
访问,但这类方法只会导入成 Swift 的实例方法。
调用方法
objc 方法的第一部分会变成 Swift 方法的方法名,之后的部分变成参数名,在 Swift 中调用时,第一个参数不需要参数名,其他参数需要些参数名。
|
|
转化后
|
|
调用的方法没有参数时,也要写上括号:
|
|
id 的兼容性
objc 中的id
类型导入 Swift 中变成Any
类型。在编译时和运行时,当 Swift 值作为id
参数传给 objc,编译器会提供通用的桥接转换操作。当id
值作为Any
导入 Swift 时,运行时自动处理。
|
|
向下转换 Any
当知道Any
类型的对象的确切类型时,可以将其向下转化(downcasting)为一个更确切的对象,但向下转换不保证可以成功。
条件类型转换操作符(as?
)返回一个可选值。
|
|
如果确定对象类型,可以用强制转换操作符(as!
):
|
|
如果强制转换错误,会导致 crash:
|
|
动态方法查找
Swift 提供AnyObject
对象,可以代表一些类型的对象,并且可以动态查找任何@any
方法。这样,对于 objc 中返回id
的方法,你可以保持无类型访问的灵活性。
|
|
未识别的选择子和可选链
调用一个AnyObject
的不存在的方法时,会导致程序 crash。Swift 利用可选方式防止这样不安全的行为。当你调用一个AnyObject
的方法时,这个方法调用会表现的像隐式解包可选值。你可以用同样的可选链的语法来在AnyObject
上调用方法。
|
|
可空和可选
objc 用nullability annotations
指定参数值、属性或返回值是否可以是NULL
或nil
。单个类型声明使用_Nullable
或_Nonnull
,单个属性声明使用nullable
、nonnull
和null_resettable
,整体域对于可空值使用NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
宏。如果没有提供可空信息,Swift 会导入为隐式解包可选值。
objc 声明:
|
|
导入 Swift:
|
|
桥接可选值与非空对象
根据可选值是否有值,Swift 将可选值桥接到非空 objc 对象。如果可选值是nil
,Swift 将其桥接为NSNull
实例。否则,桥接为解包值。可选值的数组会桥接为NSArray
。
|
|
|
|
协议限制的类(Protocol-Qualified Classes)
objc 协议限制的类导入为 Swift 中的协议类型值。
|
|
|
|
轻量级泛型(Lightweight Generics)
objc 使用lightweight generic
的类型声明导入 Swift 将提供保存内容的类型信息。
|
|
|
|
所有导入 Swift 的 objc 泛型类型参数有一个类型限制,需要那个类型是一个类(T: Any
)。
|
|
|
|
扩展
Swift 中的扩展和 objc 中的分类类似。
|
|
跟分类一样,扩展只能添加可计算的属性,不能添加要存储的属性(虽然 objc 可以用关联对象实现):
|
|
不能使用扩展重载 objc 中已有的方法和属性。
闭包
用 objc block calling convertion (标记为@convention(block)
属性),objc 的 block 可以自动导入为 Swift 的闭包。
|
|
|
|
block 和闭包是兼容的,所以可以将闭包作为 block 传给 objc 方法。而且闭包和 Swift 方法是相同类型的,所以也可以将 Swift 方法直接传过去。
闭包和 block 相似,会捕获变量,但是方式不同:变量可以修改,而不是拷贝。也就是说,objc 中的__block
是 Swift 中变量的默认行为。
捕获 self 时防止循环引用
objc 中防止循环引用:
|
|
Swift:
|
|
对象比较
在 Swift 中,两种比较对象方式:
equality(==)
:比较对象内容identity(===)
:是否指向相同的对象实例
==
的默认实现是调用isEqual:
方法,===
的默认实现是检查指针是否相等。这两个操作符不应该重载。NSObject 提供的isEqual:
的基本实现跟检查指针等同性一样,这个方法可以在子类中重载。
哈希
|
|
|
|
Swift 类型兼容
Swift-only features:
- Generics
- Tuples
- Enumerations defined in Swift without Int raw value type
- Structures defined in Swift
- Top-level functions defined in Swift
- Global variables defined in Swift
- Typealiases defined in Swift
- Swift-style variadics
- Nested types
- Curried functions
Swift API 转换成 objc 的方式与 objc API 如何装换成 Swift 类似,但反过来转换的时候:
- Swift 可选类型标注为
__nullable
- 不可选类型标注为
__nonnull
- 常量属性和计算的属性变成只读的
- 存储的变量变成读写的
- Swift type properties become Objective-C properties with the class property attribute.
- 类型方法变成类方法
- 初始化和实例方法变成实例方法
- 抛出错误的方法变成带
NSError **
变量的方法,如果 Swift 方法没有参数,AndReturnError:
添加到 objc 方法名中,否则添加error:
。如果 Swift 方法没有明确返回类型,对应的 objc 方法有一个布尔返回值。如果 Swift 方法返回非可选类型,对应的 objc 方法有一个可选的返回值。
|
|
|
|
不能在 Objective-C 中子类化一个 Swift 类。
在 objc 中配置 Swift 接口
用@objc(name)
属性可以修改你接口中暴露给 objc 的类名、属性、方法、枚举类型或枚举情况声明(enumeration case declaration)。
|
|
@objc(name)
在将 objc 项目迁移到 Swift 时也很有用。归档对象使用它们的类名进行归档,可以用@objc(name)
将名字指定为与 objc 一样,所以以前的归档可以在新的 Swift 类中解档。
Swift 还提供了 @nonobjc 属性,使声明在 objc 中不可见。You can use it to resolve circularity for bridging methods and to allow overloading of methods for classes imported by Objective-C. If an Objective-C method is overridden by a Swift method that cannot be represented in Objective-C, such as by specifying a parameter to be a variable, that method must be marked @nonobjc.
要求动态分派
当 Swift API 被 objc runtime 导入时,不能保证属性、方法、下标或初始器的动态分派。可以用dynamic
要求成员的访问通过 objc runtime 来动态分派。通常这不是必须的,但使用KVO
或method_exchangeImplementations
方法时需要这么做。
标记为
dynamic
的声明不能再用@nonobjc
标记。
选择子
在 Swift 中,objc 中的选择子用Selector
结构体表示,并且可以用#selector
表达式构造。要构造一个能被 objc 调用的方法的选择子,如#selector(MyViewController.tappedButton(sender:))
。要构造 objc 的 getter 和 setter
方法的选择子,要用getter:
或setter:
作为前缀,如#selector(getter: MyViewController.myButton)
。
|
|
An Objective-C method reference can be parenthesized, and it can use the as operator to disambiguate between overloaded functions, such as
#selector(((UIView.insert(subview:at:)) as (UIView) -> (UIView, Int) -> Void))
.
objc 方法的不安全调用
objc 中的perform(_ :)
方法不安全,Swift 中比较好的做法是将对象转换成AnyObject
,然后使用可选链。
The methods that perform a selector synchronously, such asperform(_:)
, return an implicitly unwrapped optional unmanaged pointer to an AnyObject
instance (Unmanaged<AnyObject>!
), because the type and ownership of the value returned by performing the selector can’t be determined at compile time. In contrast, the methods that perform a selector on a specific thread or after a delay, such as perform(_:on:with:waitUntilDone:modes:)
and perform(_:with:afterDelay:)
, don’t return a value.
|
|
用一个无法识别的选择子去调用一个方法时,会调用doesNotRecognizeSelector(_:)
,会默认抛出一个NSInvalidArgumentException
异常。
|
|
Keys and Key Paths
在 Swift 中,可以用#keyPath
表达式生成一个通过编译器检查的 key 和 key paths,交给 KVC 方法(value(forKey:)
和value(forKeyPath:)
)和 KVO 方法(addObserver(_:forKeyPath:options:context:)
)使用。
|
|