内容概览
- Swift 与 Objective-C 交互
- 错误处理
- 为空性标注
- 轻量级泛型
-
Kindof
类型 - 总结
Swift 与 Objective-C 交互
Swift方法什么时候暴露给Objective-C?
NSObject基类
- 方法的访问控制不是
private
级别
class MyController : UIViewController {
private func refresh() {
// ...
}
}
- 没有使用Swift特性
class MyController : UIViewController {
func refresh() -> (Int, String)? {
// ...
return (status, response)
}
}
非NSObject基类
- 在遵守 NSObjectProtocol 协议时,标注 @objc
class MyController : UIWebViewDelegate {
func webViewDidStartLoad(v: UIWebView) {
// ...
}
}
不过,这种情况发生时,Xcode会给出警告。
显式地标记
- 标记 @IBOutlet, @IBAction, @NSManaged等属性, 以支持 Interface Builder、Core Data
class MyController : UIViewController {
@IBAction private func refresh() {
// ...
}
}
- 标记
dynamic
修饰符,以支持Objective-C的Runtime,表示当前值可能在运行时被KVO更新
class MyController : UIViewController {
dynamic private var title: String? {
get { /* ... */ }
set { /* ... */ }
}
}
- 标记
@objc
修饰符,直接暴露给Objective-C
class MyController : UIViewController {
@objc private func refresh() {
// ...
}
}
标记 @objc
后,将无法使用Swift特性
Selector 冲突
Objective-C根据方法的名称区分方法,而不是方法的签名。
所以,在Swift中定义的同名方法如果需要暴露给Objective-C时,有可能会遭遇错误提示。
对于这个问题,有两种解决方案:
- 使用
@objc
属性对暴露给Objective-C的方法进行重命名
- 使用
@nonobjc
属性,禁止将方法暴露给Objective-C
函数指针
函数指针在C语言中被用作回调。但是,C语言中的函数指针不能捕获状态。
这里的self不能被C语言中的函数指针捕获,所以需要其他的地方来存储self,以供后期使用。
苹果在Swift 2.0中引入了@convention(c)属性来解决这个问题。
错误处理
常见的 Objective-C 错误处理方式 和 Swift 异常。
返回类型的差别
Objective-C 中的 id
被翻译为 Swift 中的 AnyObject
Objective-C 中的 BOOL
被翻译为 Swift 中的 Void
闭包的使用
Objective-C 中的 NSError *
被翻译为 Swift 中的 NSError?
在 Objective-C 中处理 Swift 方法抛出的错误
定义以下方法和错误类型:
在 Objective-C 中调用:
在编译时,Swift 会定义 Objective-C 需要使用的部分:
处理Cocoa中的错误
感谢苹果工程师的辛勤付出,Cocoa中的很多错误类型在Swift中已经可用。
已在Swift中可用的比较常见的Cocoa错误类型:
NSCocoaError
NSURLError
AVError
CKErrorCode
CLError
GKErrorCode
HMErrorCode
POSIXError
WKErrorCode
WatchKitErrorCode
为空性标注
请观察 UIView 在 Objective-C 中的定义:
当被转换为 Swift 1.0 的代码时,结果如下:
在转换的过程中,有很多信息是丢失了的。比如:哪些变量可以为nil
?
庆幸的是,Swift 1.1 中便解决了这个问题。
原来,苹果在 Objective-C 中引入了 nullable
, nonnull
, null_unspecified
等修饰符来解决以上问题
标明 Objective-C/C 指针是否可以为nil
,可以:
- 更好地表达 API 的意图
- 改善编译器的静态检查
- 提高 API 在 Swift 中的易用性
很快,在苹果的 SDK 中遍布了这些修饰符。
编译器可以抛出相关的警告,便于开发者及时发现这些问题。
更新后的 SDK 代码:
可以发现,苹果使用 NS_ASSUME_NONNULL_BEGIN
和 NS_ASSUME_NONNULL_END
,假设标记区域为 nonnull
。
审计区域对一些指针做出了假设:
- 单个层级的指针被假设为
nonnull
- NSError** 每个指针层级都是
nullable
然后对 nullable
和 null_unspecified
的情况进行单独标记
C 指针
- 以双下划线为前缀的为空性修饰符可以随处使用
- 为空性修饰符放在指针符号后
这里有一个细节值得注意:
外层指针为 __nullable
,*values 可以为 null。如果 numValues 为0, 你就可以传入 null。
内层指针为 __nonnull
,**values 不可以为 null。你必须传入一个非 null 的数组,用于创建 CFArray。
最后,为空性修饰符已经遍布苹果的 SDK ,建议你也使用它们来改善你的 Objective-C API。
轻量级泛型
以下 Objective-C 中的数组没有标明元素类型,对应的 Swift 代码也没有标明元素类型。
如果使用带有类型的集合,可以
- 提高 API 的表达力
- 使集合更易用
- 改善编译器的静态检查
使用方法:
- 在 < > 中指定参数类型
- 参数类型可以在这个类中尽情使用
在 Categories 和 Extensions 中也可以使用
这个特性支持向后兼容:
- 不需要改变运行时
- 对代码生成无任何影响
如果你不需要类型时,也可以很容易地去除它。
Kindof 类型
来观察一个问题:
编译器并不知道 view.subviews[0] 是一个 Button。
id
是很弱的API协议
以上代码可以将 NSApp
定义为 id
类型。
以上代码可以将 NSApp
定义为 __kindof UIApplication *
类型。
使用__kindof
可以很方便地转换父类和子类。
还可以允许向子类发送消息
__kindof
其实是更实用的id
类型
什么时候在API中使用 id
多数惯用情况下,id
可以被更准确的类型替代:
- 返回
self
的方法可以使用instancetype
- 多数集合使用场景可以对元素类型进行约束
-
__kindof X *
可以用来表达X类的子类 - 对于遵守 SomeProtocol 协议的类型,使用 id<SomeProtocol>
只在你需要表示任何类型的对象时使用id
。比如:
总结
Swift 和 Objective-C 共同进化以更好地协作
- Xcode 可以让你在这两个语言的切换中游刃有余
让你的 Objective-C 代码与时俱进
- Objective-C 新特性可以提升API的表达力
- 凭借更强的类型安全检查,你可以更快地发现问题所在
- 让你的 Objective-C 代码接口在 Swift 中更优雅
参考内容:
Swift and Objective-C Interoperability
转载请注明出处,谢谢~
网友评论