使用Swift与Cocoa和Objective-C交互-(2)与 Objective-C API 进行交互

swift 发布于 2017年12月16日

例如,在 Objective-C 中你可以这样:

UITableView _myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];

在 Swift 中你可以这样:

let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)

你不需要调用 alloc 方法;Swift 能够正确的帮你处理它。注意到当调用 Swift 风格的初始化方法时,“init” 没有出现在任何地方。

你可以在对象初始化的时候明确的制定类型,或者也可以忽略对象类型。 Swift 的类型推断能够正确的决定对象的类型。

let myTextField = UITextField(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 40.0))

这里面的UITabeViewUITextField 对象和他们在 Objective-C 中拥有相似的功能,你可以像使用 Objective-C 同样的方式来访问定义在这些类中的属性和方法。

为了一致性和简便性,Objective-C 的工厂方法都映射到了 Swift 中方便的初始化方法。这个映射允许他们被用作想初始化方法一样的简介清晰的语法。例如,在 Objective-C 中,你要像这样调用工厂方法:

UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];

在 Swift 中,你像这样调用:

let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)
访问属性

在 Swift 中使用点号语法来设置 Objective-C 对象的属性。

myTextField.textColor = UIColor.darkGrayColor()
myTextField.text = "Hello world

当获取或设置属性的时候,只需要写属性的名字,不需要写括号。但要注意darkGrayColor包含了一个括号。这是因为darkGrayColorUIColor的一个类方法,不是一个属性。

在 Objective-C 中,返回一个结果并且不带任何参数的方法,可以被当做一个隐式的getter方法,并且可以与访问属性的 getter 方法用相同的语法调用。但这不适用于 Swift。在 Swift 中,只有在 Objective-C 中用 @property语法定义的属性才被当做属性来导入。方法导入和调用的内容在 "使用方法" 这节中详细介绍。

使用方法

当在 Swift 中调用 Objective-C 方法的时候,使用点号语法。

当在 Swift 中使用 Objective-C 方法的时候,Objective-C Selector 的第一部分用作方法名并且放在括号外面。第一个参数直接放到括号里面,不需要名称。其他参数部分依照相应的参数名称直接放到括号里面。Selector 中的所有部分在调用的时候都需要。

例如,在 Objective-C 中你这样写:

[myTableView insertSubview:mySubview atIndex:2];

在 Swift 中,你这样写:

myTableView.insertSubview(mySubview, atIndex: 2)

如果你调用不带参数的方法,你仍然必须把括号加上。

myTableView.layoutIfNeeded()
id 兼容性

Swift 包含一个叫做 AnyObject 的协议类型,它代表任何类型的对象,就像 Objective-C 中的 id 一样。 AnyObject 协议能让你在维护灵活的无类型对象的同时,写出类型安全的 Swift 代码。

例如,对于 id 类型,你可以对 AnyObject 类型的常量或变量指定任何类型的对象。你也可以对这个变量重新指定其他类型的对象。

var myObject: AnyObject = UITableViewCell()
myObject = NSDate()

你还能在不进行强制类型转换的情况下调用任何 Objective-C 的方法和访问任何的属性。这包含用 @objc 属性标记的 Objective-C 兼容方法。

let futureDate = myObject.dateByAddingTimeInterval(10)
let timeSinceNow = myObject.timeIntervalSinceNow

然而,因为只有在运行时 AnyObject 类型的对象里面的具体类型才会明确,这样就很容以写出不安全的代码。另外,和 Objective-C 比起来,如果你调用了 Anyobject 中不存在的方法或属性,就会产生运行时错误。例如,如下代码不会产生编译错误,但会在运行时出现未定义的方法错误。

myObject.characterAtIndex(5)
// crash, myObject doesn't respond to that method

然而,你可以利用 Swift 中的 Optional 特性来消除你代码中得这些错误。当你在 AnyObject 的对象中调用 Objective-C 方法时,这个方法实际上被当做未封装的 Optional 来调用。你可以像在 AnyObject 中选择性地调用 Optional 方法一样,来使用相同的 optional 链式语法。这个过程同样适用于属性。

例如,在下面列出的代码中,第一行和第二行代码没有被执行,因为 NSDate 对象中不存在 count 属性和 characterAtIndex: 方法。myLength 常量被推断为 optional Int 类型,并且设置为 nil。你还可以用 if-let 语句来条件性的解包对象没有响应的方法结果,就像是第三行中得代码那样。

let myCount = myObject.count?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}

在 Swift 中进行向下转型的过程当中,从 AnyObject 到一个更加具体类型的转换,不一定成功并且这个转换会返回一个 optional 类型的值。你可以检测这个 optional 值来决定类型转换是否成功。

let userDefaults = NSUserDefaults.standardUserDefaults()
let lastRefreshDate: AnyObject? = userDefaults.objectForKey("LastRefreshDate")
if let date = lastRefreshDate as? NSDate {
println("\(date.timeIntervalSinceReferenceDate)")
}

当然,如果你能确保这个对象所属的类型(并且知道它不是 nil),你可以强制的调用 as 操作符进行转换。

let myDate = lastRefreshDate as NSDate
let timeInterval = myDate.timeIntervalSinceReferenceDate
关于nil

在 Objective-C 中,你使用原始的指针来处理对象的引用,它可以是 NULL 值(在 Objective-C 中也被称为 nil)。 在 Swift 中,所有的值,包括对结构体和对象的引用-都确保是非 nil 值。另外,你可以通过将值得类型包装成 optional 类型来表示那些可以为空得值。当你需要表示一个值缺失,你使用 nil 值。关于 optional 的更多信息,可以查看 《Swift编程语言》中 Optionals 这章。

因为 Objective-C 不保证对象一定是非空的, Swift 对引入的 Objective-C API 中所有参数类型中的类,还有返回类型都标记为 Optional。在你使用 Objective-C 对象之前,你要保证它里面不是空的。

在一些情况下,你可能会 绝对 的确定 Objective-C 的方法或属性不会返回一个 nil 的对象类型。为了让这种特殊情况下的对象能够更方便的被处理,Swift 引入了隐式拆箱的 Optional类型。隐式拆箱的 Optional 包含了 Optional 类型的所有安全特性。另外,你在不进行 nil 检测和拆箱的情况下,直接的访问这个值。当你访问这种类型的 Optional 的值得时候不进行安全的拆箱操作的话,隐式拆箱的 Optional 会检测这个值是否为空。如果这个值为空,就会产生一个运行时错误。所以,你需要自己对隐式拆箱的 Optional 值进行检测和拆箱,除非你能确定这个值不为空。

Extension扩展

Swift 中的 Extension Objective-C 的 Category 类似。Extension 扩展了现存的类,结构体,枚举的行为,包括那些在 Objective-C 中定义的。你可以在系统框架或是你自定义的类型上定义 Extension。只需要简单的导入相应的模块,并且引用你在 Objective-C 中同样的类,结构体和枚举名称。

例如,你可以通过扩展 UIBezierPath 类来基于一个变成和起始点来创建一个等边三角形为形状的简单的贝塞尔曲线。

extension UIBezierPath {
convenience init(triangleSideLength: Float, origin: CGPoint) {
self.init()
let squareRoot = Float(sqrt(3.0))
let altitude = (squareRoot * triangleSideLength) / 2
moveToPoint(origin)
addLineToPoint(CGPoint(x: triangleSideLength, y: origin.x))
addLineToPoint(CGPoint(x: triangleSideLength / 2, y: altitude))
closePath()
}
}

你可以使用 Extension 来添加属性(包括类属性和静态属性)。然而,这个数据必须是被计算出来的;Extension 不能为类,结构体和枚举添加存储属性。

这个示例扩展了 CGRect 结构体,添加了一个计算出来的 area 属性:

extension CGRect {
var area: CGFloat {
return width * height
}
}
let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
let area = rect.area
// area: CGFloat = 500.0

你还可以使用 Extension 来让类在不继承的情况下添加一个协议。如果这个协议是在 Swift 中定义的,你还可以将他们添加到结构体或枚举中,无论是定义在 Swift 中的还是 Objective-C 中的。

你不能用 Extension 来重定义 Objective-C 类型中存在的方法。

Closure

Objective-C 中得 block 在 Swift 中被引入为 Closure。例如,这里是 Objective-C 中的 block 变量。

void (^completionBlock)(NSData *, NSError *) = ^(NSData *data, NSError *error) {/* ... */}

这是他们在 Swift 中的样子:

let completionBlock: (NSData, NSError) -> Void = {data, error in /* ... */}

Swift 中的 Closure 和 Objective-C 中的 block 是相互兼容的,所以你可以将 Swift 的 Closure 传递到需要 block 类型的 Objective-C 方法中。Swift 的 Closure 和函数类型相同,所以你甚至可以将 Swift 中的函数名传入。

Closure 在大部分语义上都和 block 相似,但有一点不同:变量是可修改的,而不是拷贝的。换句话说,在 Swift 中的默认行为相当于 Objective-C 中 __block 关键字。

对象比较

在 Swift 中,比较对象有两种方式。第一种,相等性(==),比较对象直接的内容。 第二种,相同性(===),确定常量或变量是否引用的相同对象的实例。

在 Swift 中通常用 == 和 === 操作符来比较 Swift 和 Objective-C 对象的相等性。 Swift 对继承自 NSObject 的对象提供了一个默认的实现。在这个操作符的实现中, Swift 调用了定义在 NSObject 中的 isEqual: 方法。NSObject 类仅进行相同性比较,所以你应该对继承自 NSObject 的类实现你自己的 isEqual: 方法。因为你可以将 Swift 对象(包括哪些不继承自NSObject)传递进 Objective-C API,如果你需要 Objective-C API 比较对象的内容而不是引用,那么你就需要为这些类实现 isEqual:方法。

作为实现相等性的一部分,确保依照对象比较的规则实现 hash 属性。另外如果想要将你的类用作字典的键,那么还要使用 Hashable 协议并实现 hashValue 属性。

Swift 类型兼容性

当你定义的 Swift 类继承自 NSObject 或者其他 Objective-C 类的时候,这个类就自动与 Objective-C 兼容。所有相关这一切的步骤都已经由 Swift 编译器帮你完成。如果你从未在 Objective-C 代码中导入 Swift 类,你不需要对关于类型兼容性的问题着急。另外,如果你的 Swift 类没有继承自 Objective-C 类型并且你还希望在 Objective-C 代码中使用它,那么你可以用下面提到的 @objc 属性。

@objc 属性能让你的 Swift API 在 Objective-C 和 Objective-C 运行时中可用。换句话说,你可以在任何你想在 Objective-C 代码中用到的 Swift 方法,属性或者类前面加上 @objc 属性。如果你的类继承自 Objective-C 类,编译器会帮你加入这个属性。编译器还会将用@objc 属性标记的类中的所有属性和方法加上这个@objc属性。当你使用@IBOuntlet,@IBAction,@NSManaged属性的时候,@objc 也会被增加上。当你 Objective-C 的类中使用 selelctor 来实现 target-action 设计模式的时,这个 @objc 属性也很有用- 例如,NSTimerUIButton

当你在 Objective-C 中使用 Swift API 的时候,编译器会进行直接的转换。例如,Swift API func playSong(name:String) 会在 Objective-C 中被导入成 - (void) playSong:(NSString *) name。当你在 Objective-C 中使用 Swift 的初始化方法的时候,编译器会添加方法前面添加 “initWith”,并对之前的初始化方法进行合适的大写处理。例如,Swift 初始化方法 init (songName: String, artist: String) 会在 Objective-C 中被导入成 - (instancetype) initWithSongName:(NSString *) songName artist:(NSString *) artist

Swift 提供了一个 @objc 的变体,来让你指定你在 Objective-C 中的符号名。例如,如果你的 Swift 类名中包含 Objective-C 中布支持的字符,你可以提供一个在 Objective-C 中到替代名称。如果你要为 Swift 函数提供 Objective-C 名称,要使用 selector 语法。当 selector 中含有参数的时候,记得添加一个冒号(:)

@objc(Squirrel)
class Белка {
@objc(initWithName:)
init (имя: String) { /*...*/ }
@objc(hideNuts:inTree:)
func прячьОрехи(Int, вДереве: Дерево) { /*...*/ }
}

当你在 Swift 中使用 @objc(<#name#>) 属性的时候,这个类就在 Objective-C 中可用了,不需要任何命名空间。最后,当你将可归档的 Objective-C 类迁移到 Swift 的时候胡这个属性也很有用。因为被归档的类将他们的类名称存储到归档中,你应该使用 @objc(<#name#>) 属性来指定和你 Objective-C 类名相同的名称,这样之前的归档就可以在新的 Swift 类中解档。

Objective-C Selector

Objective-C selector 是一个指向 Objective-C 方法的类型。在 Swift 中,Objective-C Selector 使用 Selector 结构来表示。你可以通过字符串值来构造一个 selector,比如 let mySelector: Selector = “tappedButton:”。因为字符串值能够自动被转换成 selector,你可以将字符串值传递到任意一个接受 selector 的方法。

import UIKit
class MyViewController: UIViewController {
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))

init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
myButton.addTarget(self, action: "tappedButton:", forControlEvents: .TouchUpInside)
}

func tappedButton(sender: UIButton!) {
println("tapped button")
}
}

注意 performSelector:方法和它调用的方法不会被导入到 Swift,因为他们是不安全的。

如果你的 Swift 类继承自 Objective-C 类,那么这个类中的所有方法和属性都在 Objective-C 中可用。另外,如果你的 Swift 类没有继承自 Objective-C 类,那么你需要把你想在 Objective-C 中用到的方法用 @objc 标记上,就像 《Swift 类型兼容性中提到的》。


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

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