Swift 5.1 新特性

在 7 月 29 日的发布的 Xcode 11 beta 5 中,包含了 Swift 5.1。如果想要体验这些新特性,需要至少安装好这个版本的 Xcode。本文内容主要参考 Raywenderlich这篇文章 编写,如果想要查看原文,请点击链接查看。

Swift 5.1 在 5.0 引入的 ABI 稳定性基础上增加了模块稳定性。虽然 ABI 稳定性在运行时考虑到应用程序的兼容性,但模块稳定性在编译时支持库的兼容性。这意味着你可以在任何编译器版本中使用第三方框架,而不只是构建它的那个版本。

下面我们一起看一下有哪些改进。

模糊的结果类型 (Opaque Result Types)

在开发的时候,有时候可能会使用 protocol 作为返回值类型。下面来看一个例子:

protocol BlogPost {
    var title: String { get }
    var author: String { get }
}

struct Tutorial: BlogPost {
    let title: String
    let author: String
}

func createBlogPost(title: String, author: String) -> BlogPost {
    guard !title.isEmpty && !author.isEmpty else {
        fatalError("No title and/or author assigned!")
    }
    return Tutorial(title: title, author: author)
}

let swift4Tutorial = createBlogPost(title: "What's new in Swift 4.2?",
                                    author: "Cosmin Pupăză")
let swift5Tutorial = createBlogPost(title: "What's new in Swift 5?",
                                    author: "Cosmin Pupăză")

上面代码:1)首先定义 BlogPost 协议;2)定义 Tutorial 并实现 BlogPost 协议;3)定义 createBlogPost() 方法用于创建 BlogPost;4)用 createBlogPost() 创建 swift4Tutorialswift5Tutorial 两个实例。

下面我们想比较 swift4Tutorialswift5Tutorial 是否相等:

// 错误:Binary operator '==' cannot be applied to two 'BlogPost' operands
let isSameTutorial = (swift4Tutorial == swift5Tutorial)

因为 BlogPost 还没有实现 Equatable,所以会出错。下面让 BlogPost 继承自 Equatable

protocol BlogPost: Equatable {
    var title: String { get }
    var author: String { get }
}

这时另一个错误出现在 createBlogPost() 方法:

// Protocol 'BlogPost' can only be used as a generic constraint because it has Self or associated type requirements
func createBlogPost(title: String, author: String) -> BlogPost {
    guard !title.isEmpty && !author.isEmpty else {
        fatalError("No title and/or author assigned!")
    }
    return Tutorial(title: title, author: author)
}

这个错误的意思是 BlogPost 只能用来作为泛型约束,因为它有 Self 或者有关联类型要求。查看 Equatable的定义,确实是有 Self

public protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}

具有关联类型的协议不是类型,即使他们看起来是类型。而它们更像是类型占位符,这个类型可以是任何实现了我这个协议的类型。

在 Swift 5.1 中,我们就可以在返回值类型前面加上 some 来解决这个问题。把 createBlogPost() 方法改为:

func createBlogPost(title: String, author: String) -> some BlogPost {
    guard !title.isEmpty && !author.isEmpty else {
        fatalError("No title and/or author assigned!")
    }
    return Tutorial(title: title, author: author)
}

some 的作用就是告诉编译器我这个方法的返回值可以是实现了 BlogPost 的任何类型。

修改完成之后,我们直接用 == 比较 swift4Tutorialswift5Tutorial 就不会报错了。

在 SwiftUI 中,就是使用了 some

struct ContentView: View {
    var body: some View {
        Text("Hello World")
    }
}

隐式返回

在 Swift 5.1 中,如果方法体只有一行语句,则可以省略 return

func myName() -> String {
    "Lebron"
}

属性包装器

在 Swift 5.1 之前,使用计算属性时,可能出现类似下面的代码:

var settings = ["swift": true, "latestVersion": true]

struct Settings {
    var isSwift: Bool {
        get {
            return settings["swift"] ?? false
        }
        set {
            settings["swift"] = newValue
        }
    }
    
    var isLatestVersion: Bool {
        get {
            return settings["latestVersion"] ?? false
        }
        set {
            settings["latestVersion"] = newValue
        }
    }
}

var newSettings = Settings()
newSettings.isSwift
newSettings.isLatestVersion
newSettings.isSwift = false
newSettings.isLatestVersion = false

上面的代码中,如果有更多的计算属性,那么就要写跟多的重复代码。为了解决这个问题,Swift 5.1 引入了属性包装器,可以把上面的代码简写为:

var settings = ["swift": true, "latestVersion": true]

@propertyWrapper
struct SettingsWrapper {
  let key: String
  let defaultValue: Bool

  var wrappedValue: Bool {
    get {
      settings[key] ?? defaultValue
    }
    set {
      settings[key] = newValue
    }
  }
}

struct Settings {
  @SettingsWrapper(key: "swift", defaultValue: false)         var isSwift: Bool
  @SettingsWrapper(key: "latestVersion", defaultValue: false) var isLatestVersion: Bool
}
  • @propertyWrapperSettingsWrapper 标记为属性包装器。作为一个属性包装器,必须有一个名为wrappedValue 的属性。
  • 使用 @SettingsWrapper 标记 Settings中对应的属性。

在 struct 中定义属性的默认值

在 Swift 5.1 前,如果想要给 struct 的属性定义默认值,必须这么写:

struct Author {
    let name: String
    var tutorialCount: Int
    
    init(name: String, tutorialCount: Int = 0) {
        self.name = name
        self.tutorialCount = tutorialCount
    }
}

let author = Author(name: "George")

而在 Swift 5.1 以后,可以直接像 class 那样给属性定义默认值:

struct Author {
    let name: String
    var tutorialCount = 0
}

使用 Self 调用静态成员

在 Swift 5.1 以前,需要使用 类名.静态成员来调用静态成员:

struct Editor {
    static func reviewGuidelines() {
        print("Review editing guidelines.")
    }
    
    func edit() {
        Editor.reviewGuidelines()
        print("Ready for editing!")
    }
}

而在 Swift 5.1 中,可以直接用 Self.静态成员

struct Editor {
    static func reviewGuidelines() {
        print("Review editing guidelines.")
    }
    
    func edit() {
        Self.reviewGuidelines()
        print("Ready for editing!")
    }
}

创建未初始化的数组

Swift 5.1 给 Array 添加了一个新的初始化方法:init(unsafeUninitializedCapacity:initializingWith:)

let randomSwitches = Array<String>(unsafeUninitializedCapacity: 5) { buffer, count in
    for i in 0..<5 {
        buffer[i] = Bool.random() ? "on" : "off"
    }
    // 必须给 `count` 赋值,否则 `randomSwitches` 会变成空数组
    count = 5
}

staticclass 下标

在 Swift 5.1 中可以定义 staticclass 下标:

@dynamicMemberLookup
class File {
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    // 定义 static 下标
    static subscript(key: String) -> String {
        switch key {
        case "path":
            return "custom path"
        default:
            return "default path"
        }
    }
    
    // 使用 Dynamic Member Lookup 重写上面的下标
    class subscript(dynamicMember key: String) -> String {
        switch key {
        case "path":
            return "custom path"
        default:
            return "default path"
        }
    }
}

File["path"] // "custom path"
File["PATH"] // "default path"
File.path    // "custom path"
File.PATH    // "default path"

@dynamicMemberLookup 标记 File 是为了可以使用点语法来访问自定义的下标。

Keypath 支持动态成员查找

struct Point {
    let x, y: Int
}

@dynamicMemberLookup
struct Circle<T> {
    let center: T
    let radius: Int
    
    // 定义泛型下标,可以用 keypath 来访问 `center` 的属性
    subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U {
        center[keyPath: keyPath]
    }
}

let center = Point(x: 1, y: 2)
let circle = Circle(center: center, radius: 1)
circle.x // 1
circle.y // 2

Tuple 支持 Keypath

struct Instrument {
    let brand: String
    let year: Int
    let details: (type: String, pitch: String)
}

let instrument = Instrument(
    brand: "Roland",
    year: 2019,
    details: (type: "acoustic", pitch: "C")
)
// 使用 keypath 访问 tuple
let type = instrument[keyPath: \Instrument.details.type]
let pitch = instrument[keyPath: \Instrument.details.pitch]

weakunowned 属性自动实现 EquatableHashable

class Key {
    let note: String
    
    init(note: String) {
        self.note = note
    }
}

extension Key: Hashable {
    static func == (lhs: Key, rhs: Key) -> Bool {
        lhs.note == rhs.note
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(note)
    }
}

class Chord {
    let note: String
    
    init(note: String) {
        self.note = note
    }
}

extension Chord: Hashable {
    static func == (lhs: Chord, rhs: Chord) -> Bool {
        lhs.note == rhs.note
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(note)
    }
}

struct Tune: Hashable {
    unowned let key: Key
    weak var chord: Chord?
}

在 Swift 5.1 以前,Tune 的定义里面会报错:没有实现 EquatableHashable;在 Swift 5.1 则已经自动实现。

不明确的枚举 case

如果有不明确的枚举 case,在 Swift 5.1 中会产生警告⚠️。

enum TutorialStyle {
  case cookbook, stepByStep, none
}

// 会产生警告
let style: TutorialStyle? = .none

因为 styleOptional 类型,编译器不知道 .noneOptional.none 还是 TutorialStyle.none ,所有要写具体一点,例如:let style: TutorialStyle? = TutorialStyle.none

匹配可选类型的枚举

在 Swift 5.1 以前,switch 语句中匹配可选类型的枚举时,case 后面需要加问号:

enum TutorialStatus {
    case written, edited, published
}

let status: TutorialStatus? = .published

switch status {
    case .written?:
        print("Ready for editing!")
    case .edited?:
        print("Ready to publish!")
    case .published?:
        print("Live!")
    case .none:
        break
}

而在 Swift 5.1 中,可以把问号去掉:

switch status {
    case .written:
        print("Ready for editing!")
    case .edited:
        print("Ready to publish!")
    case .published:
        print("Live!")
    case .none:
        break
}

Tuple 类型的转换

let temperatures: (Int, Int) = (25, 30)
let convertedTemperatures: (Int?, Any) = temperatures

在 Swift 5.1 以前,(Int, Int) 是不能转换成 (Int?, Any) 的,而在 Swift 5.1 可以。

Any 和泛型参数的方法重载

func showInfo(_: Any) -> String {
  return "Any value"
}

func showInfo<T>(_: T) -> String {
  return "Generic value"
}

showInfo("Swift")

在 Swift 5.1 以前,showInfo("Swift") 返回的是 Any value;而在 Swift 5.1 中,返回的是 Generic value。也就是说在 Swift 5.1 以前,Any 参数类型的方法优先;而在 Swift 5.1 中,泛型参数的方法优先。

autoclosure 参数可以定义别名

在 Swift 5.1 中,@autoclosure 标记的 closure 参数可以使用别名:

struct Closure<T> {
    typealias ClosureType = () -> T
    
    func apply(closure:  @autoclosure ClosureType) {
        closure()
    }
}

在 Swift 5.1 以前,只能这样写:

struct Closure<T> {
    func apply(closure: @autoclosure () -> T) {
        closure()
    }
}

在 Objective-C 方法中返回 self

在 Swift 5.1 以前,被 @objc 标记的方法中返回 self,必须继承自 NSObject

class Clone: NSObject {
    @objc func clone() -> Self {
        return self
    }
}

在 Swift 5.1 中,则无需集成 NSObject

class Clone {
  @objc func clone() -> Self {
    self
  }
}

想及时看到我的新文章的,可以关注我。同时也欢迎加入我管理的Swift开发群:536353151

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容