引言
Swift,苹果于2014年WWDC(苹果开发者大会)发布的新开发语言,可与Objective-C共同运行于Mac OS和iOS平台,用于搭建基于苹果平台的应用程序。Swift吸收了众多现代编程语言的优点,尽力的提供简洁的编程语言和强大的功能。
WWDC 2017 给大家带来了很多惊喜。Swift 4 也伴随着 Xcode 9 测试版来到了我们的面前,很多强大的新特性非常值得我们期待在正式项目中去使用它。因为 Swift 4 是开源的,如果你关注 swift-evolution 这个项目的话,就应该已经提前了解到它的新特性了。本文参考了 WWDC 2017 以及各种资料,,从语法、字符串、标准库、构建过程等方面,把 Swift 4 的这些新特性一一列举出来做介绍和分析,让他们毫无保留地展现在你眼前,下面话不多说了,来随着小编一起看看详细的介绍吧。
一、语法改进
extension 中可以访问 private 的属性
考虑以下代码:
struct Date: Equatable, Comparable { private let secondsSinceReferenceDate: Double static func ==(lhs: Date, rhs: Date) -> Bool { return lhs.secondsSinceReferenceDate == rhs.secondsSinceReferenceDate } static func <(lhs: Date, rhs: Date) -> Bool { return lhs.secondsSinceReferenceDate < rhs.secondsSinceReferenceDate }}
上面代码定义了一个 Date 结构体,并实现 Equatable 和 Comparable 协议。为了让代码更清晰,可读性更好,一般会把对协议的实现放在单独的 extension 中,这也是一种非常符合 Swift 风格的写法,如下:
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。
struct Date { fileprivate let secondsSinceReferenceDate: Double}...
但是如果用 fileprivate,属性的作用域就会比我们需要的更大,可能会不小心造成属性的滥用。
在 Swift 4 中,private 的属性的作用域扩大到了 extension 中,并且被限定在了 struct 和 extension 内部,这样就不需要再改成 fileprivate 了,这是最好的结果。
类型和协议的组合类型
考虑以下代码:
protocol Shakeable { func shake()}extension UIButton: Shakeable { /* ... */ }extension UISlider: Shakeable { /* ... */ }func shakeEm(controls: [???]) { for control in controls where control.state.isEnabled { } control.shake()}
在 Swift 3 中,这里的 ??? 应该写什么呢?如果写 UIControl,那么 control.shake() 就会报错;如果写 Shakeable,那么 control.state.isEnabled 就会报错。其实我们也可以这样写:
func shakeEm(controls: [UIControl]) { for control in controls where control.isEnabled { if control is Shakeable { (control as! Shakeable).shake() } }}
这样写虽然可以跑通了,但是很丑陋。
在 Swift 4 中,可以把类型和协议用 & 组合在一起作为一个类型使用,就可以像下面这样写了:
protocol Shakeable { func shake()}extension UIButton: Shakeable { /* ... */ }extension UISlider: Shakeable { /* ... */ }func shakeEm(controls: [UIControl & Shakeable]) { for control in controls where control.state.isEnabled { control.shake() }// Objective-C API@interface NSCandidateListTouchBarItem<CandidateType> : NSTouchBarItem@property (nullable, weak) NSView <NSTextInputClient> *client;@end}
把它声明为了 UIControl & Shakeable 类型。OK,圆满解决。
PS:
这个代码例子是 WWDC 2017 的 PPT 中的,上面的代码有点问题,control.state.isEnabled 这句代码中,state 是没有 isEnabled 这个属性的,改为 control.isEnabled 就可以了。看来苹果的工程师做 PPT 有时候还是不太严谨。
另外,iOS SDK 中的 API 也用这个特性做了优化,例如:
// Objective-C API@interface NSCandidateListTouchBarItem<CandidateType> : NSTouchBarItem@property (nullable, weak) NSView <NSTextInputClient> *client;@end
这个 API 的 Objective-C 版本是没有问题的,可以知道 client 属性既是一个 NSView,又符合 NSTextInputClient 协议。然而它对应的 Swift 3 版本为:
class NSCandidateListTouchBarItem<CandidateType: AnyObject> : NSTouchBarItem { var client: NSView?}
仅仅是一个 NSView 类型 /(ㄒoㄒ)/~~
在 Swift 4 中,这类 API 做了优化,改成了:
class NSCandidateListTouchBarItem<CandidateType: AnyObject> : NSTouchBarItem { var client: (NSView & NSTextInputClient)?}
这样类型的声明就更加严谨了。
Associated Type 可以追加 Where 约束语句
在 Swift 4 中可以在 associatedtype 后面声明的类型后追加 where 语句
associatedtype Element where <xxx>
看下面是 Swift 4 标准库中 Sequence 中 Element 的声明:
protocol Sequence { associatedtype Element where Self.Element == Self.Iterator.Element // ...}
它限定了 Sequence 中 Element 这个类型必须和 Iterator.Element 的类型一致。
通过 where 语句可以对类型添加更多的约束,使其更严谨,避免在使用这个类型时做多余的类型判断。
新的 Key Paths 语法
先来看看 Swift 3 中 Key Paths 的写法:
@objcMembers class Kid: NSObject { dynamic var nickname: String = "" dynamic var age: Double = 0.0 dynamic 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)
Swift 4 中创建一个 KeyPath 用 `` 作为开头:
/Kid.nickname
当编译器可以推导出类型时,可以省略基础类型部分:
/.nickname
上面的代码在 Swift 4 中就可以这样写:
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 具有以下优势:
下标支持泛型
有时候会写一些数据容器,Swift 支持通过下标来读写容器中的数据,但是如果容器类中的数据类型定义为泛型,以前的下标语法就只能返回 Any,在取出值后需要用 as? 来转换类型。Swift 4 定义下标也可以使用泛型了。
struct GenericDictionary<Key: Hashable, Value> { private var data: [Key: Value] init(data: [Key: Value]) { self.data = data } subscript<T>(key: Key) -> T? { return data[key] as? T }}let dictionary = GenericDictionary(data: ["Name": "Xiaoming"])let name: String? = dictionary["Name"] // 不需要再写 as? String
二、字符串
Unicode 字符串在计算 count 时的正确性改善
在 Unicode 中,有些字符是由几个其它字符组成的,比如 é 这个字符,它可以用 /u{E9} 来表示,也可以用 e 字符和上面一撇字符组合在一起表示 /u{65}/u{301}。
考虑以下代码:
var family = "
注:相关教程知识阅读请移步到swift教程频道。
新闻热点
疑难解答