学习《Using Swift with Cocoa and Objective-C》的笔记。

Interoperability

Interacting with Objective-C APIs

Initialization

在Swift中初始化一个Objective-C的类:

  • Objective-C中的init方法要删掉init前缀,只是表明这个方法是一个构造器。
  • initWith开头的要连“With”一起删除。
  • 删掉initWith之后的selector分片的第一个字母改成小写。并且作为参数名。
  • 不需要调用alloc
1
UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
1
let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)
  • Objective-C中的工厂方法被映射为Swift中的convenience initializer
1
UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];
1
let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)

Failable Initialization

Objective-C的构造器可以返回nil告知初始化失败,Swift通过可失败构造器达到相同的目的。Objective-C通过引入Nullability and Optionals语法表明构造器是不是可失败的,如果不可失败,Swift构造器是init,否则init?

1
2
3
4
5
if let image = UIImage(contentsOfFile: "MyImage.png") {
// loaded the imagesuccessfully
} else {
// could not load the image
}

Accessing Properties

  • Objective-C中不同nullability特性的属性(nonnullnullablenull-resettable)分别映射到Swift中的optionalnonoptional属性。
  • Objective-C的readonly属性导入到Swift变成{ get }的计算属性。
  • Objective-C的weak属性导入到Swift变成weak var属性。
  • Objective-C的assigncopystrongunsafe_unretained属性导入到Swift变成相应的存储属性。
  • Objective-C中的atomicnonatomic在Swift中被忽略,全部都是nonatomic
  • Objective-C中的getter=setter=在Swift中被忽略。

Working with Methods

  • Objective-C中selector的第一部分作为相应Swift方法的方法名出现在括号外。
  • 剩余的selector分片变成相应的参数名出现在括号内。
  • 所有的selector分片在调用的时候都是必须有的。
1
2
3
4
[myTableView insertSubview:mySubview atIndex:2];
```
```swift
myTableView.insertSubview(mySubview, atIndex: 2)

id Compatibility

Swift有一个AnyObject协议用来表示任意类型的对象,等价于Objective-C中的id。可以对其调用任意Objective-C的方法和访问任意属性而不需要强制转换成特定类型。

1
2
3
4
5
var myObjective: AnyObject = UITableViewCell()
myObject = NSDate()

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

访问AnyObject不存在的方法或属性会触发runtime error,但是可以利用Swift的可选链消除错误。另外,访问AnyObject的属性总是返回一个可空值。

1
let myChar = myObject.characterAtIndex?(5)

也可以强制转换AnyObject到一个特定类型:

1
2
3
4
let lastRefreshDate: AnyObject? = userDefaults.objectForKey("LastRefreshDate")
if let date = lastRefreshDate as? NSDate {
// do something
}

Nullability and Options

Objective-C中引用对象的指针可以是nil,Swift中所有的对象都是非空的,如果要表示值缺失需要用optional类型。
Objective-C引入了nullability记号来表示一个参数、属性或者返回值是否可以nil

  • 声明为_Nonnull或者包裹在NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END之间的类型被导入为Swift中的non-optional类型。
  • 声明为_Nullable的类型被导入为Swift中的optional类型。
  • 不带nullability记号的类型被导入为Swift肿的隐式optional类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@property (nullable) id nullableProperty;
@property (nonnull) id nonNullProperty;
@property id unannotatedProperty;

NS_ASSUME_NONNULL_BEGIN
- (id)returnsNonNullValue;
- (void)takesNonNullParameter:(id)value;
NS_ASSUME_NONNULL_END

- (nullable id)returnsNullableValue;
- (void)takeNullableParameter:(nullable id)value;

- (id)returnsUnannotatedValue;
- (void)takesUnannotatedParameter:(id)value;

导入为Swift:

1
2
3
4
5
6
7
8
9
10
11
12
var nullableProperty: AnyObject?
var nonNullProperty: AnyObject
var unannotatedProperty: AnyObject!

func returnsNonNullValue() -> AnyObject
func takesNonNullParameter(value: AnyObject)

func returnsNullableValue() -> AnyObject?
func takesNullableParameter(value: AnyObject?)

func returnsUnannotatedValue() -> AnyObject!
func takesUnannotatedParameter(value: AnyObject!)

Extensions

Swift的Extension和Objective-C的Category类似。但是不能用Extension重写Objective-C中已经存在的方法和属性。

Closures

Objective-C中的block被导入为Swift中的closure

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

等价于:

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

可以把Swift的closure传给Objective-C中需要block的方法作为参数。

closure捕获变量和block有一点不同:Objective-C中的__block行为在Swift是默认的。

Object Comparison

Swift的==比较运算符会调用Objective-C中NSObject定义的isEqual:方法。对于从NSObject继承来的类,应该实现相应的isEqual:方法。如果想要把对象用作字典的key,还需要实现Hashable协议中的hashValue属性。

Swift Type Compatibility

当从Objective-C的类创建一个Swift的类的时候,它的所有成员——属性、方法、下标和构造器都是可用的。但是如果要把Swift的类(不是从Objective-C的类继承来的,或者想改变借口的名字)暴露给Objective-C,就需要显式的指定@objc属性。

Exposing Swift Interface in Objective-C

如果Swift的类是从NSObject或者任意Objective-C的类继承来的,那这个类就自动和Objective-C兼容。否则,用@objc放在Swift的方法、属性、下标、构造器,或者类、枚举的声明之前。@objc还能为方法指定其他名称

1
2
3
4
5
6
7
8
9
10
11
@objc(SomeClass)
class 中文类名: NSObject {
@objc(initWithName:)
init(中文参数名: String) {
// ...
}
@objc(hideNuts:inTree:)
func 中文方法(参数1: Int, 参数2: Int) {
// ...
}
}

Lightweight Generics

1
2
3
@property NSArray<NSDate *> *dates;
@property NSSet<NSString *> *words;
@property NSDictionary<NSURL *, NSData *> *cachedData;

Swift将导入为:

1
2
3
var dates: [NSDate]
var words: Set<String>
var cachedData: [NSURL: NSData]

除了Foundation Collection的类以外,Objective-C中的lightweight generics都会被Swfit忽略。

Objective-C Selectors

在Swift中,Objective-C的Selector被结构体Selector所表示。可以直接用字符串字面量构造一个Selector:let mySelector: Selector = "tappedButton:"。因为字符串字面量可以自动转换为Selector,所以可以把一个字符串字面量直接传给任意接收Selector参数的方法:

1
myButton.addTarget(self, action: "tappedButton:", forControlEvents: .TouchUpInside)

Writing Swift Classes with Objective-C Behavior

Inheriting from Objective-C Classes

Swift中可以直接定义Objective-C中类的子类。

1
2
3
4
import UIKit
class MySwiftViewController: UIViewController {
// ...
}

如果要重写父类方法,需要加上override关键字。

Adopting Protocols

Swift可以采用Objective-C中定义的protocol。

1
2
3
class MySwiftViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// ...
}

因为Swift中类的名字空间和protocol的名字空间是统一的,所以在Swift中,NSObject协议被映射为NSObjectProtocol

Integrating with Interface Builder

Swift中使用outlets和actions,只需要在属性前插入@IBOutlet或者@IBAction关键字。@IBOutlet使得属性编程隐式可选。

1
2
3
4
5
6
7
class MyViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet var textFields: [UITextField]!
@IBAction func buttonTapped:(_: AnyObject) {
print("button tapped!")
}
}

Specifying Property Attributes

Strong and Weak

Swift属性默认是strong的。用weak关键字指示属性是弱引用,并且该属性是可选类型。

Read/Write and Read-Only

Swift里没有readwritereadonly属性,如果声明存储属性,用let表示只读,用var表示可读可写。如果是计算属性,{ get }表示只读,{ get set }可读可写。

Copy Semantics

Swift中,Objective-C的copy被转换成了@NSCopying,也就是说该属性必须符合NSCopying协议。

Using Swift Class Names with Objective-C APIs

Swift的类都放在他们所在的module的全局的命名空间里,在Objective-C引用也一样。例如,一个叫MyFramework的framework中的一个Swift的类DataManager的名字是MyFramework.DataManager

1
let muyPersonClass: AnyClass = NSClassFromString("MyGreatApp.Person")

Working with Cocoa Data Types

Swift会自动的将一些Objective-C的类型转换为Swift的类型,也会把一些Swfit类型转换为Objective-C的类型,也有一些是可以直接互换的。

String

Swift自动的在StringNSString类型之间转换,不应该在Swift中使用NSString
当Swift导入Objective-C的API的时候,会把所有的NSString类型替换成String。当Objective-C使用Swift类的时候,会把所有String类型替换成NSString。只需要import Foundation就可以开启两种类型的桥接。

Localization

Swift中用一个函数NSLocalizedString(key:tableName:bundle:value:comment:)替换掉了Objective-C一组宏(NSLocalizedStringNSLocalizedStringFromTableNSLocalizedStringFromTableInBundleNSLocalizedStringWithDefaultValue),其中为tableNamebundlevalue都提供了默认值。

Numbers

Swift自动把IntFloat等数值类型桥接到NSNumber。可以用这些数值类型创建NSNumber

1
2
let n = 42
let m: NSNumber = n

Collection Classes

Swift把NSArrayNSSetNSDictionary分别桥接到ArraySetDictionary

Arrays

如果NSArray指定了参数化类型,则对应到Swift中的[ObjectType],否则对应到[AnyObject]。可以用as?或者as![AnyObject]转换到[SomeType]

1
2
3
4
let swiftArray = foundationArray as [AnyObject]
if let downcastedSwiftArray = swiftArray as? [NSView] {
// ...
}

如果要把Swift数组转换成NSArray,数组里的元素必须是符合AnyObject协议的。将Swift API导入Objective-C的时候,会把所有Array替换成NSArray

Sets

Set<ObjectType>对应NSSet<ObjectType>,如果没有指定ObjectType,则对应到Set<AnyObject>

Dictionaries

NSDictionary如果没有指定参数化类型,则对应到[NSObject: AnyObject]

Errors

Swift把ErrorType(协议)和NSError进行桥接。

1
2
3
@objc public enum CustomError: Int, ErrorType {
case A, B, C
}

对应到相应的Objective-C的声明:

1
2
3
4
5
6
typedef SWIFT_ENUM(NSInteger, CustomError) {
CustomErrorA = 0,
CustomErrorB = 1,
CustomErrorC = 2,
};
static String * const CustomErrorDomain = @"Project.CustomError"

Foundation Data Types

Swift对Foundation Framework中的数据类型做了封装。

1
2
3
4
let size = CGSize(width: 20, height: 40)
let rect = CGRect(x: 50, y: 50, width: 100, height: 100)
let width = rect.width // 等价于CGRectGetWidth(rect)
let maxX = rect.maxY // 等价于CGRectGetMaxY(rect)

还把NSUIntegerNSInteger桥接到Int

Core Foundation

重映射类型:

Swift导入Core Foundation类型的时候,编译器吧类型名中的Ref移除。

内存管理

Swift中使用Core Foundation中annotated的API返回的对象会自动处理内存管理,不需要调用CFRetainCFRelase或者CFAutorelase
其他情况需要处理Unmanaged<Instance>

错误处理

Cocoa中,会产生错误的方法把NSError指针作为最后一个参数,如果Objective-C方法的最后一个非block的参数是NSError **,Swift就把它替换成throws关键字,如果同时还是第一个关键字,会尝试简化方法名字,比如删除WithError或者AndReturnError后缀。如果这样的方法返回BOOL值表示方法成功或者失败,Swift会把返回值改成Void,如果返回nil表示失败,Swift会把返回值类型替换成non-optional类型。

1
- (BOOL)removeItemAtURL:(NSURL *)URL error:(NSError **)error;

1
func removeItemAtURL(URL: NSURL) throws

Key-Value Observing

Swift中可以对继承自NSObject的类使用KVO,用dynamic关键字表明属性是需要observe的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyObjectToObserve: NSObject {
dynamic var myDate = NSDate()
func updateDate() {
myDate = NSDate()
}
}

private var myContext = 0

class MyObserver: NSObject {
var objectToObserve = MyObjectToObserve()
override init() {
super.init()
objectToObserve.addObserver(self, forKeyPath:"myDate", options: .New, context: &myContext)
}

override func observeValueForKeyPath(keyPath:String?, ofObject object: AnyObject?, change: [String: AnyObject]?, context: UnsafeMutablePointer<Void>) {
// ...
}

deinit {
objectToObserve.removeObserver(self, forKeyPath:"MyDate", context: &myContext)
}
}

Singleton

1
2
3
4
5
6
7
class Singleton {
static let sharedInstance: Singleton = {
let instance = Singleton()
// setup code
return instance
}()
}