[精校版]Using Swift with Cocoa and Objective-C--互用性-与ObjC API交互

发布于:2014-06-10 15:12阅读数:

互用性是让Swift和Objective-C相接合的一种特性,使你能够在一种语言编写的文件中访问和使用另一种语言编写的代码。当你准备开始把Swift融入到你的开发流程中时,你应该懂得如何利用互用性来

Swift系列文章由CocoaChina翻译小组翻译自苹果的官方文档:Using Swift with Cocoa and Objective-C--Interoperability--Interacting with Objective-C APIs。本篇译者:@Creolophus (git主页),敬请勘误,欢迎在 CocoaChina github主页 查看更多文章!
 

互用性是让Swift和Objective-C相接合的一种特性,使你能够在一种语言编写的文件中访问和使用另一种语言编写的代码。当你准备开始把Swift融入到你的开发流程中时,你应该懂得如何利用互用性来重新定义并提高你写Cocoa应用的方式。 互用性很重要的一点就是当你在写Swift代码时使用Objective-C的API接口。当你导入一个Objective-C框架后,你可以使用原生的Swift语法实例化它的Class并且与之交互。

 
初始化(Initialization)
为了使用Swift实例化Objective-C的Class,你应该使用Swift语法调用它的一个初始化器。当Objective-C的init方法变化到Swift,它们用Swift初始化语法呈现。"init"前缀被截断当作一个关键字,用来表明该方法是初始化方法。那些以“initWith”开头的init方法,“With”也会被去除。从“init”或者“initWith”中分离出来的这部分方法名首字母变成小写,并且被当做是第一个参数的参数名。其余的每一部分方法名依次变为参数名。这些方法名都在圆括号中被调用。
 
举个例子,你在使用Objective-C时会这样做:
  1. UITableView *myTableView = [[UITableView alloc]  
  2. initWithFrame:CGRectZero style:UITableViewStyleGrouped]; 
 
在Swift中,你应该这样做:
  1. let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped) 
 
你不需要调用alloc,Swift能替你处理。注意,当使用Swift风格的初始化函数的时候,”init”不会出现。 
 
你可以在初始化时显式地声明对象的类型,或者也可以忽略它,Swift的类型接口能够正确判断对象的类型。
  1. //Swift 
  2. let myTextField = UITextField(frame: CGRect(0.0, 0.0, 200.0, 40.0)) 
这里的UITableView和UITextField对象和你在Objective-C中使用的具有相同的功能。你可以用样的方式使用他们,包括访问属性或者调用各自的类中定义的方法。
 
为了统一和简易,Objective-C的工厂方法也在Swift中映射为方便的初始化方法。这种映射能够让他们使用同样简洁明了的初始化方法。例如,在Objective-C中你可能会像下面这样调用一个工厂方法:
  1. //Objective-C 
  2. UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0]; 
在Swift中,你应该这样做:
  1. //Swift 
  2. let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0) 
 
访问属性(Accessing Properties)
在Swift中访问和设置Objective-C对象的属性时,使用点语法
  1. ///Swift 
  2. myTextField.textColor = UIColor.darkGrayColor() 
  3. myTextField.text = "Hello world" 
  4. if myTextField.editing { 
  5.     myTextField.editing = false 
当getting或setting属性时,直接使用属性名称,不需要附加圆括号。注意,darkGrayColor后面附加了一对圆括号,这是因为darkGrayColor是UIColor的一个类方法,不是一个属性。 在Objective-C中,一个有返回值的无参数方法可以被作为一个隐式的访问函数(implicit getter),并且可以与访问器使用同样的方法调用。但在Swift中不再能够这样做了,只有使用Objective-C中@property语法编写的属性才能被作为属性引入。详细请参看Working with Methods

使用方法(Working with Methods)
在Swift中调用Objective-C方法时,使用点语法。 当Objective-C方法转换到Swift时,Objective-C的selector的第一部分将会成为基本方法名并出现在圆括号的前面,而第一个参数将直接在括号中出现,并且没有参数名,而剩下的参数名与参数则一一对应地填入圆括号中。
 
举个例子,你在使用Objective-C时会这样做:
  1. //Objective-C 
  2. [myTableView insertSubview:mySubview atIndex:2]; 
在Swift中,你应该这样做:
  1. //Swift 
  2. myTableView.insertSubview(mySubview, atIndex: 2) 
如果你调用一个无参方法,仍必须在方法名后面加上一对圆括号
  1. //Swift 
  2. myTableView.layoutIfNeeded() 
 
id 兼容性(id Compatibility)
Swift包含一个叫做AnyObject的协议类型,用以表示任意类型的对象,就像Objective-C中的id一样。AnyObject协议允许你编写类型安全的Swift代码,同时维持无类型对象的灵活性。因为AnyObject协议也保证了这种安全,Swift将id对象导入为AnyObject。 
 
举个例子,跟id一样,你可以为AnyObject类型的对象分配任何其他类型的对象,你也同样可以为它重新分配其他类型的对象。
  1. //Swift 
  2. var myObject: AnyObject = UITableViewCell() 
  3. myObject = NSDate() 
你也可以在调用Objective-C方法或者访问属性时不将它转换为具体类的类型。这包括了Objcive-C中标记为@objc的方法。
  1. //Swift 
  2. let futureDate = myObject.dateByAddingTimeInterval(10) 
  3. let timeSinceNow = myObject.timeIntervalSinceNow 
然而,由于直到运行时才知道AnyObject的对象类型,所以有可能在不经意间写出不安全代码。另外,与Objective-C不同的是,如果你调用的方法或者访问的属性没有经AnyObject对象声明,运行时将会报错。比如下面的代码在运行时将会报出一个未被识别的selector error:
  1. //Swift 
  2. myObject.characterAtIndex(5) 
  3. // crash, myObject does't respond to that method 
但是,你可以通过Swift的optinals特性来排除这个Objective-C中常见的错误。当你用AnyObject对象调用一个Objective-C方法时,这次调用将会变成一次隐式展开optional(implicitly unwrapped optional)的行为。你可以通过optional特性来决定AnyObject类型的对象是否调用该方法,同样的,你可以把这种特性应用在属性上。
 
举个例子,在下面的代码中,第一和第二行代码将不会被执行,因为length属性和characterAtIndex:方法不存在于NSDate对象中。myLength常量会被推测成可选的Int类型并且被赋值为nil。同样你可以使用if-let声明来有条件的展开这个方法的返回结果,从而判断对象是否能执行这个方法。就像第三行做的一样。
  1. //Swift 
  2. let myLength = myObject.length? 
  3. let myChar = myObject.characterAtIndex?(5) 
  4. if let fifthCharacter = myObject.characterAtIndex(5) { 
  5.     println("Found \(fifthCharacter) at index 5"
对于Swift中的强制类型转换,从AnyObject转换为更特殊的对象类型并不会保证成功,所以它会返回一个可选值。而你需通过检查该值的类型来确认转换是否成功。
  1. //Swift 
  2. let userDefaults = NSUserDefaults.standardUserDefaults() 
  3. let lastRefreshDate: AnyObject? = userDefaults.objectForKey("LastRefreshDate"
  4. if let date = lastRefreshDate as? NSDate { 
  5.     println("\(date.timeIntervalSinceReferenceDate)"
当然,如果你能确定这个对象的类型(并且确定不是nil),你可以添加as操作符强制调用。
  1. //Swift 
  2. let myDate = lastRefreshDate as NSDate 
  3. let timeInterval = myDate.timeIntervalSinceReferenceDate 
 
使用nil(Working with nil
在Objective-C中,对象的引用可以是值为NULL的原始指针(同样也是Objective-C中的nil)。而在Swift中,所有的值–包括结构体与对象的引用都被保证为非空。作为替代,你将这个可以为空的值包装为optional type。当你需要宣告值为空时,你需要使用nil。你可以在Optionals中了解更多。 
 
因为Objective-C不能保证一个对象是non-nil的,所以Swift在引入Objective-C的API的时候,确保了所有函数的返回类型与参数类型都是optional。在使用Objective-C的API之前,你应该检查并保证该值非空。在某些情况下,你可能绝对确认某些Objective-C方法或者属性永远不应该返回一个nil的对象引用。为了让对象在这种情况下更加易用,Swift使用implicitly unwrapped optionals方法引入对象,implicitly unwrapped optionals 包含optional类型的所有安全特性。此外,你可以直接访问对象的值而无需检查nil或者自己展开它。当你访问这种类型的变量时,implicitly unwrapped optional 首先检查这个对象的值是否不存在,如果值不存在,那它将会抛出一个运行时错误。因此,你通常需要检查和展开一个implicitly unwrapped optional,除非你确定值不会为空。
 
扩展(Extensions)
Swift的扩展和Objective-C的类别(Category)相似。扩展为原有的类、结构和枚举丰富了功能,包括在Objective-C中定义过的。你可以为系统的框架或者你自己的类型定义扩展。简单要导入合适的模块并且保证你在Objective-C中使用的类、结构或枚举拥有相同的名字。 举个例子,你可以扩展UIBezierPath类通过正三角形来创建一个简单的Bézier路径,这个方法只需提供三角形的边长与起点。
  1. //Swift 
  2. extension UIBezierPath { 
  3.     convenience init(triangleSideLength: Float, origin: CGPoint) { 
  4.         self.init() 
  5.         let squareRoot = Float(sqrt(3)) 
  6.         let altitude = (squareRoot * triangleSideLength) / 2 
  7.         moveToPoint(origin) 
  8.         addLineToPoint(CGPoint(triangleSideLength, origin.x)) 
  9.         addLineToPoint(CGPoint(triangleSideLength / 2, altitude)) 
  10.         closePath() 
  11.     } 
你也可以使用扩展来增加属性(包括类与静态属性)。然而,这些属性必须是通过计算才能获取的,扩展不会为类,结构体以及枚举添加存储属性。下面这个例子为CGRect类增加了一个经计算过的area属性。
  1. //Swift 
  2. extension CGRect { 
  3.     var area: CGFloat { 
  4.     return width * height 
  5.     } 
  6. let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0) 
  7. let area = rect.area 
  8. // area: CGFloat = 500.0 
你同样可以使用扩展来为类添加协议而无需对它进行子类化。如果这个协议是在Swift中被定义的,你可以添加comformance到它的结构或枚举中,无论它们是在Objective-C或在Swift中被定义。你不能使用扩展来覆盖Objective-C类型中存在的方法与属性。
 
闭包(Closures)
Objective-C中的blocks会被自动导入为Swift中的闭包。例如,下面是一个Objective-C 中的block变量:
  1. //Objective-C 
  2. void (^completionBlock)(NSData *, NSError *) = ^(NSData *data, NSError *error) {/* ... */
而它在Swift中的形式为:
  1. //Swift 
  2. let completionBlock: (NSData, NSError) -> Void = {data, error in /* ... */
Swift的闭包与Objective-C中的blocks能够兼容,所以你可以把一个Swift闭包传递给一个把block作为参数的Objective-C方法。Swift闭包与函数具有相同的类型,所以你甚至可以传递Swift函数的名字。 闭包与blocks语义上想通,但在一个地方不同:变量是可以直接改变的,但不是像block那样会拷贝变量。换句话说,Swift中变量的默认行为与Objective-C中__block变量一致。
 
对象比较(Object Comparison)
如果你在Swift比较两个对象,那么会有两种不同的类型的比较。第一个equality 相等(==)用于比较对象的内容,第二个identity 恒等(===)用以决定常量或者变量是否引用同一个对象实例。在Swift中比较Swift和Objective-C对象使用==和===运算符。Swift为源自NSObject类的对象提供了默认的==实现。在该运算符的实现中,Swift调用了NSObject类的isEqual:方法。NSObject类仅实现identity (===)比较,所以你应该在源自NSObject的类中实现你自己的isEqual:方法。
 
由于你可以把Swift对象(包括不是源自NSObject的对象)传递给Objective-C API,所以如果你想要Objective-C API比较对象的内容时,你应该实现isEqual:方法。作为实现类相等的一部分,要确保根据Object comparison中的规则实现hash属性。更进一步说,如果你想要在字典中把类用作键,那么也要遵照Hashable协议,并实现hashValue属性。
 
Swift类型兼容性(Swift Type Compatibility)
当你定义了一个继承自NSObject或者其他Objective-C 类的Swift类,这些类会自动兼容Objective-C。所有的步骤都有Swift编译器自动完成,如果你从未在Objective-C代码中导入Swift 类,你也不需要担心类型适配问题。另外一种情况,如果你的Swift类并不来源自Objectve-C类,而且你希望能在Objecive-C的代码中使用它,你可以使用下面描述的@objc属性。@objc可以让你的Swift API在Objective-C和Objective-C runtime中使用。换句话说,你可以在任何Swift方法、类、属性前添加@objc,来使得他们可以在Objective-C代码中使用。如果你的类继承自Objective-C,编译器会自动帮助你完成这一步。编译器还会在类的所有方法和属性前加@objc,如果这个类自己前面加上了@objc关键字。当你使用@IBOutlet,@IBAction,或者是@NSManaged属性时,@objc也会添加在前面。当你使用selector实现target-action设计模式时,这个属性也会非常有用,例如,NSTimer或者UIButton。
 
当你在Objective-C中使用Swift API,编译器基本对语句做直接的翻译。例如,Swift API func playSong(name: String)(name: String)在Objective-C会被解释为- (void)playSong:(NSString *)name。然而,有一个例外:当在Objective-C中使用Swift的初始化函数,编译器会在方法前添加”initWith”,并且将原初始化函数的第一个参数首字母大写。例如,这个Swift初始化函数init (songName: String, artist: String将被翻译为- (instancetype)initWithSongName:(NSString *)songName artist:(NSString *)artist 。 Swift同时也提供了一个@objc关键字的变体,允许你为Objective-C中的符号指定名称。例如,如果你的Swift 类的名字包含Objecytive-C中不支持的字符,你就可以为Objective-C提供一个可供替代的名字。如果你给Swift函数提供一个Objcetiv-C名字,请使用Objective-C selector syntax。要记得为带参数的函数添加(:)
  1. //Swift 
  2. @objc(Squirrel) 
  3. class Белка { 
  4.     @objc(initWithName:) 
  5.     init (имя: String) { /*...*/ } 
  6.     @objc(hideNuts:inTree:) 
  7.     func прячьОрехи(Int, вДереве: Дерево) { /*...*/ } 
当你在Swift类中使用@objc(<#name#>)属性,这个类可以不需要命名空间即可在Objective-C中使用。这个属性在你迁徙Objecive-C代码到Swift时同样也非常有用。由于归档过的对象存贮了类的名字,你应该使用@objc(<#name#>)来指定和Objective-C类一样的名字,这样旧的归档可以在新Swift类中恢复。

Objective-C选择器(Selectors)
Objective-C selector是指向一个Objective-C方法名的类型。在Swift中,Objective-C selector被Selector结构体替代。你可以通过字符串字面量创建一个选择器,比如let mySelector: Selector = "tappedButton:"。因为字符串字面量能够自动转换为选择器,所以你可以把字符串字面量直接传递给任何接受选择器的方法。
  1. //Swift 
  2. import UIKit 
  3. class MyViewController: UIViewController { 
  4.     let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50)) 
  5.   
  6.     init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) { 
  7.         super.init(nibName: nibName, bundle: nibBundle) 
  8.         myButton.targetForAction("tappedButton:", withSender: self) 
  9.     } 
  10.   
  11.     func tappedButton(sender: UIButton!) { 
  12.         println("tapped button"
  13.     } 
注意:performSelector:方法和相关的调用选择器的方法没有导入到Swift中,因为它们是不安全的。
 
如果你的Swift类继承自Objective-C的类,那么该类的所有方法和属性都可以用作Objective-C的选择器。另外,如果你的Swift类不是继承自Objective-C,如果你想要当选择器来使用你就需要在前面添加@objc关键字,详情请看Swift Type Compatibility

 

CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!

请搜索微信号“CocoaChina”关注我们!

搜索CocoaChina微信公众号:CocoaChina

顶部