原文地址:https://github.com/easyui/blog/blob/master/Swift/2017-09-26-Swift-Tips%5BV4.0%2B%2CXcode9.0%2B%5D.md
:smile:Xcode 9 中同时集成了 Swift 3.2 和 Swift 4。
- Swift 3.2 完全兼容 Swift 3.1,并会在过时的语法或函数上报告警告。
- Swift 3.2 具有 Swift 4 的一些写法,但是性能不如 Swift 4。
- Swift 3.2 和 Swift 4 可以混合编译,可以指定一部分模块用 Swift 3.2 编译,一部分用 Swift 4 编译。
- 迁移到 Swift 4 后能获得 Swift 4 所有的新特性,并且性能比 Swift 3.2 好。
:smile:dynamic
Swift 3中dynamic是自带@objc,但是Swift 4中,dynamic不在包含@objc了。所以有些需要使用到@objc标明的方法,在Swift 4得补回去。
:smile:@objc
在项目中想把 Swift 写的 API 暴露给 Objective-C 调用,需要增加 @objc。在 Swift 3 中,编译器会在很多地方为我们隐式的加上 @objc,例如当一个类继承于 NSObject,那么这个类的所有方法都会被隐式的加上 @objc。swift
class MyClass: NSObject {
func print() { ... } // 包含隐式的 @objc
func show() { ... } // 包含隐式的 @objc
}
这样很多并不需要暴露给 Objective-C 也被加上了 @objc。大量 @objc 会导致二进制文件大小的增加。
在 Swift 4 中,隐式 @objc 自动推断只会发生在很少的当必须要使用 @objc 的情况,比如:
1、复写父类的 Objective-C 方法
2、符合一个 Objective-C 的协议
其它大多数地方必须手工显示的加上 @objc。
减少了隐式 @objc 自动推断后,Apple Music app 的包大小减少了 5.7%。
:smile:The use of Swift 3 @objc inference in Swift 4 mode is deprecated. Please address deprecated @objc inference warnings, test your code with “Use of deprecated Swift 3 @objc inference” logging enabled, and then disable inference by changing the "Swift 3 @objc Inference" build setting to "Default" for the "ProjectName" target.
到target->build setting->swift3 @objc inference 设置为off
:smile:枚举更swift化
AVMetadataCommonKeyTitl -> AVMetadataKey.commonKeyTitle
AVMetadataKeySpaceCommon -> AVMetadataKeySpace.common
:smile:AVPlayerLayer的videoGravity属性(其实是iOS11的变化)
swift3swift
/*!
@property videoGravity
@abstract A string defining how the video is displayed within an AVPlayerLayer bounds rect.
@discusssion Options are AVLayerVideoGravityResizeAspect, AVLayerVideoGravityResizeAspectFill
and AVLayerVideoGravityResize. AVLayerVideoGravityResizeAspect is default.
See
*/
open var videoGravity: String
swift4swift
/*!
@property videoGravity
@abstract A string defining how the video is displayed within an AVPlayerLayer bounds rect.
@discusssion Options are AVLayerVideoGravityResizeAspect, AVLayerVideoGravityResizeAspectFill
and AVLayerVideoGravityResize. AVLayerVideoGravityResizeAspect is default.
See
*/
open var videoGravity: AVLayerVideoGravity
:smile:extension 中可以访问 private 的属性
struct Date {
private let secondsSinceReferenceDate: Double
}
extension Date: Equatable {
static func ==(lhs: Date, rhs: Date) -> Bool {
return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate
}
}
extension Date: Comparable {
static func <(lhs: Date, rhs: Date) -> Bool {
return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate
}
}
但是在 Swift 3 中,编译就报错了,因为 extension 中无法获取到 secondsSinceReferenceDate 属性,因为它是 private 的。于是在 Swift 3 中,必须把 private 改为 fileprivate,且在同一个文件里。
在 Swift 4 中,private 的属性的作用域扩大到了 extension 中
:smile: Associated Type 可以追加 Where 约束语句
protocol Sequence {
associatedtype Element where Self.Element == Self.Iterator.Element
// ...
}
它限定了 Sequence 中 Element 这个类型必须和 Iterator.Element 的类型一致。
通过 where 语句可以对类型添加更多的约束,使其更严谨,避免在使用这个类型时做多余的类型判断。
:smile: 类型和协议的组合类型
在 Swift 4 中,可以把类型和协议用 & 组合在一起作为一个类型使用,就可以像下面这样写了:
```swift
protocol Shakeable {
func shake()
}
extension UIButton: Shakeable { /* ... / }
extension UISlider: Shakeable { / ... */ }
func shakeEm(controls: [UIControl & Shakeable]) {
for control in controls where control.isEnabled {
control.shake()
}// Objective-C API
@interface NSCandidateListTouchBarItem : NSTouchBarItem
@property (nullable, weak) NSView *client;
@end
}
```
在 Swift 4 中,这类 API 做了优化,改成了这样类型的声明就更加严谨了:swift
class NSCandidateListTouchBarItem
var client: (NSView & NSTextInputClient)?
}
:smile: KVC:新的 Key Paths 语法
Swift 3 中 Key Paths 的写法:
```swift
class Kid: NSObject {
@objc var nickname: String = ""
@objc var age: Double = 0.0
@objc var friends: [Kid] = []
}
var ben = Kid(nickname: "Benji", age: 5.5)
let kidsNameKeyPath = #keyPath(Kid.nickname)
let name = ben.valueForKeyPath(kidsNameKeyPath)
ben.setValue("Ben", forKeyPath: kidsNameKeyPath)
```
上面是在swift3中使用的方法,在oc中能够很好的运行,可在swift中它有着明显的不足:
1.返回值是Any类型,错误的赋值可能导致运行时错误
2.这个类必须继承NSObject,而swift是可以不继承自任何类的
Swift 4 中创建一个 KeyPath 用 \
作为开头:
\Kid.nickname
上面的代码在 Swift 4 中就可以这样写:
```swift
struct Kid {
var nickname: String = ""
var age: Double = 0.0
var friends: [Kid] = []
}
var ben = Kid(nickname: "Benji", age: 8, friends: [])
let name = ben[keyPath: \Kid.nickname]
ben[keyPath: \Kid.nickname] = "BigBen"
```
相比 Swift 3,Swift 4 的 Key Paths 具有以下优势:
- 类型可以定义为 class、struct
- 定义类型时无需加上 @objcMembers、dynamic 、@objc等关键字
- 性能更好
- 类型安全和类型推断,例如 ben.valueForKeyPath(kidsNameKeyPath) 返回的类型是 Any,ben[keyPath: \Kid.nickname] 直接返回 属性的 类型
- 可以在所有值类型上使用
:smile: KVO
依然只有 NSObject 才能支持 KVO。
Swift 4中的一个对此有影响的改变是继承 NSObject 的 swift class 不再默认全部 bridge 到 OC。
然而 KVO 又是一个纯 OC 的特性,所以如果是 swift class 需要在声明的时候增加 @objcMembers 关键字。否则在运行的时候你会得到一个 error:
fatal error: Could not extract a String from KeyPath
Swift.ReferenceWritableKeyPath
一个好消息是不需要在对象被回收时手动 remove observer。但是这也带来了另外一个容易被忽略的事情:观察的闭包没有被强引用,需要我们自己添加引用,否则当前函数离开后这个观察闭包就会被回收了。KVO 之后返回的是一个 NSKeyValueObservation 实例,需要自己控制这个实例的生命周期。
```swift
@objcMembers class OCClass: NSObject {
dynamic var name: String
init(name: String) {
self.name = name
}
}
class ViewController: UIViewController {
var swiftClass: OCClass!
var ob: NSKeyValueObservation!
override func viewDidLoad() {
super.viewDidLoad()
swiftClass = OCClass(name: "oc")
ob = swiftClass.observe(\.name) { (ob, changed) in
let new = ob.name
print(new)
}
swiftClass.name = "swift4"
}
}
```
:smile: 下标支持泛型
有时候会写一些数据容器,Swift 支持通过下标来读写容器中的数据,但是如果容器类中的数据类型定义为泛型,以前的下标语法就只能返回 Any,在取出值后需要用 as? 来转换类型。Swift 4 定义下标也可以使用泛型了。
```swift
struct GenericDictionary {
private var data: [Key: Value]
init(data: [Key: Value]) {
self.data = data
}
subscript(key: Key) -> T? {
return data[key] as? T
}
}
let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])
let name: String? = dictionary["Name"] // 不需要再写 as? String
```
:smile: Unicode 字符串在计算 count 时的正确性改善
在 Unicode 中,有些字符是由几个其它字符组成的,比如 é 这个字符,它可以用 \u{E9} 来表示,也可以用 e 字符和上面一撇字符组合在一起表示 \u{65}\u{301}。
考虑以下代码:
```swift
var family = "