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:))使用。
|
|