Swift 5.3

记录 Swift 5.3 的新特性!

1. String 添加了一个初始化方法

可以直接从 UTF8 array, 初始化一个 String

let validUTF8: [UInt8] = [67, 97, 102, 128 + (128 - 61), 128 + (128 - 87), 0]
let s = String(unsafeUninitializedCapacity: validUTF8.count,
               initializingUTF8With: { ptr in
                ptr.initialize(from: validUTF8)
                return validUTF8.count
               })
print(s)

// print
Café

2. enum 添加 Comparable 默认实现

Swift 5.3 之后, enum 的大小是根据你定义时候的顺序决定的, 从大到小一次定义的,
就是最开始定义的是最大的, 如果莫哥 case 中含有参数, 那么这个参数必须也实现了Comparable, 根据正常的理解一样, 不同参数还有依次的排序, 如下代码, 一看就懂了:

enum Membership: Comparable {
    case premium(Int)
    case preferred
    case general
}

var members: [Membership] = [.preferred, .premium(1), .general, .premium(0), .premium(-2)]
print(members.sorted(by: { $0 > $1 }))

// print
[__lldb_expr_17.Membership.general, 
__lldb_expr_17.Membership.preferred, __lldb_expr_17.Membership.premium(1), __lldb_expr_17.Membership.premium(0), __lldb_expr_17.Membership.premium(-2)]

3. getter 不会自动在 didSet 中调用, 除非在 didSet 中访问了 oldValue

对比下面两段代码, 第一段在 Swift5.3 之前, 会挂掉, Swift5.3之后不会
Swift5.3 在第二段这种情况才会挂掉

代码1:
@propertyWrapper
struct Delayed<Value> {
  var wrappedValue: Value {
    get {
      guard let value = value else {
        preconditionFailure("Property \(String(describing: self)) has not been set yet")
      }
      return value
    }

    set {
      guard value == nil else {
        preconditionFailure("Property \(String(describing: self)) has already been set")
      }
      value = newValue
    }
  }
  
  var value: Value?
}

class Foo {
  @Delayed var bar: Int {
    didSet { print("didSet called") }
  }
}

let foo = Foo()
foo.bar = 1
// print
// Swift 5.3
didSet called
// 小于 Swift 5.3
Fatal error: Property Delayed<Int>(value: nil) has not been set yet: file __lldb_expr_13/MyPlayground.playground, line 68
代码2:
@propertyWrapper
struct Delayed<Value> {
  var wrappedValue: Value {
    get {
      guard let value = value else {
        preconditionFailure("Property \(String(describing: self)) has not been set yet")
      }
      return value
    }

    set {
      guard value == nil else {
        preconditionFailure("Property \(String(describing: self)) has already been set")
      }
      value = newValue
    }
  }
  
  var value: Value?
}

class Foo {
  @Delayed var bar: Int {
    didSet { print("didSet called \(oldValue)") }
  }
}

let foo = Foo()
foo.bar = 1

// print
Fatal error: Property Delayed<Int>(value: nil) has not been set yet: file __lldb_expr_13/MyPlayground.playground, line 68

4. 在闭包中的隐式 self 的使用

  1. 可以把 [self] 添加到闭包的捕获列表里面, 或者闭包的参数是 self 的一个方法
class Test {
    var x = 0
    func execute(_ work: @escaping () -> Void) {
        work()
    }
    func method() {
        execute(inc)
        execute { [self] in
            inc()
        }
    }

    func inc() {
        x += 1
    }
}
  1. 如果 self 是值类型, 不需要添加到捕获列表里面, self 就可以隐式使用
struct Test {
    var x = 0
    func execute(_ work: @escaping () -> Void) {
        work()
    }

    func method() {
        execute {
            run()
        }
    }
    
    func run() {
        print("run")
    }
}

var t = Test()
t.method()

// print 
run
  1. 题外话, 值类型如何在逃逸闭包里面修改自己的属性, 智障
struct Test {
    var x = 0
    func execute(_ work: @escaping () -> Void) {
        work()
    }

    mutating func method() {
        var result = self
        execute {
            result.x += 1
            print(result.x)
        }
        self = result
    }
}

var t = Test()
t.method()

5. catch 可以分类了

enum TaskError: Error {
    case someRecoverableError
    case someFailure(String)
    case anotherFailure(String)
}

func performTask() throws {
    throw TaskError.someFailure("???????")
}

do {
  try performTask()
} catch TaskError.someRecoverableError {    // OK
    print("someRecoverableError")
} catch TaskError.someFailure(let msg),
        TaskError.anotherFailure(let msg) { // Also Allowed
    print(msg)
}

6. 添加了 Float16 类型

Float16

7. 尾闭包的样式修改

经历了很多样式, 最后现在为:

func resolve(
  id: Int,
  action: (Int) -> Void,
  completion: (() -> Void)? = nil,
  onError: ((Error) -> Void)? = nil
) {
  
}

resolve(id: 0) { _ in
    
}

resolve(id: 0) { _ in
    
} onError: { _ in
    
}

8. enum 实现 protocol

具体的实现参考下面代码, 目前无参数的方法, 无法通过 case 来实现, 其他的需要对应格式实现, 如下:

protocol Foo {
  static var zero: FooEnum { get }
  static var one: Self { get }
  static func two(arg: Int) -> FooEnum
  static func three(_ arg: Int) -> Self
  static func four(_ arg: String) -> Self
  static var five: Self { get }
  static func six(_: Int) -> Self
  static func seven(_ arg: Int) -> Self
  static func eight() -> Self
}

enum FooEnum: Foo {
    static func eight() -> FooEnum {
        return .eight
    }
    
    case zero // okay
    case one // okay
    case two(arg: Int) // okay
    case three(_ arg: Int) // okay
//    case four(arg: String) // not a match
    case four(_ arg: String) // okay
//    case five(arg: Int) // not a match
    case five // okay
    case six(Int) // okay
    case seven(Int) // okay
    case eight // not a match
}

9. @main 入口

@main
@NSApplicationMain与@main 在 Swift5.3 之后相同
其他还有为一些脚本使用

  1. 桌面新建一个叫 Command.swift 的 swift 文件
@main
struct Command {
    static func main() {
        print("run")
    }
}

  1. cmd 在桌面文件夹下
swiftc -parse-as-library Command.swift

得到可执行文件, 双击, 得到

/Users/xxxx/Desktop/Command ; exit;
run
  1. 如果报错, 建议新建一个文件夹, 在这个文件夹下操作, 因为 build 的时候, 会查找当前文件夹和子文件夹, 如果存在多个 @main 就会报错
  2. 使用 swift package
swift package init --type executable

会初始化一个 SPM, 其中包含一个 main.swift

swift build
swift run

// print
Hello, world!

如果使用xxx.swift替换 main.swift, 并且在 xxx.swift 中添加 @main,
swift build 便不好用
所以说 SPM, 并没有应用 @main 的这种方式来构建
@main is no longer usable due to misdetection of top level code

10. 新增#filePath #fileID

隐私问题, #file 不注重隐私, 所以替换这两个

目标是, 如下代码:

print(#file)
print(#filePath)
fatalError("Something bad happened!")

MagicFile/0274-magic-file.swift
/Users/brent/Desktop/0274-magic-file.swift
Fatal error: Something bad happened!: file MagicFile/0274-magic-file.swift, line 3

Swift 5.3 目前是, 如下代码:

print(#file)
print(#fileID)
print(#filePath)
fatalError("Something bad happened!")

/Users/brent/Desktop/0274-magic-file.swift
MagicFile/0274-magic-file.swift
/Users/brent/Desktop/0274-magic-file.swift
Fatal error: Something bad happened!: file MagicFile/0274-magic-file.swift, line 3

Swift 5.3 assert, precondition, fatalError 输出的都是 #fileID, 而不是#file.
Swift 5.3 目前推荐使用 #fileID, 1 节省空间, 2 安全, 暴露更少的细节.
Ease the transition to concise magic file strings

11.Swift Package Manager 添加 resources,多语言

  1. resources
public static func target(
    name: String,
    dependencies: [Target.Dependency] = [],
    path: String? = nil,
    exclude: [String] = [],
    sources: [String]? = nil,
    resources: [Resource]? = nil,   // <=== NEW
    publicHeadersPath: String? = nil,
    cSettings: [CSetting]? = nil,
    cxxSettings: [CXXSetting]? = nil,
    swiftSettings: [SwiftSetting]? = nil,
    linkerSettings: [LinkerSetting]? = nil
) -> Target

SPM 会自动生成一个 internal extension

extension Bundle {
    /// The bundle associated with the current Swift module.
    static let module: Bundle = { ... }()
}

使用方法

// Get path to DefaultSettings.plist file.
let path = Bundle.module.path(forResource: "DefaultSettings", ofType: "plist")

// Load an image that can be in an asset archive in a bundle.
let image = UIImage(named: "MyIcon", in: Bundle.module, compatibleWith: UITraitCollection(userInterfaceStyle: .dark))

// Find a vertex function in a compiled Metal shader library.
let shader = try mtlDevice.makeDefaultLibrary(bundle: Bundle.module).makeFunction(name: "vertexShader")

// Load a texture.
let texture = MTKTextureLoader(device: mtlDevice).newTexture(name: "Grass", scaleFactor: 1.0, bundle: Bundle.module, options: options)
  1. 多语言
public init(
    name: String,
    defaultLocalization: LocalizationTag = nil, // New defaultLocalization parameter.
    pkgConfig: String? = nil,
    providers: [SystemPackageProvider]? = nil,
    products: [Product] = [],
    dependencies: [Dependency] = [],
    targets: [Target] = [],
    swiftLanguageVersions: [Int]? = nil,
    cLanguageStandard: CLanguageStandard? = nil,
    cxxLanguageStandard: CXXLanguageStandard? = nil
)

使用:Foundation 会自动根据当前的语言,来获取对应的语言

// Get path to a file, which can be localized.
let path = Bundle.module.path(forResource: "TOC", ofType: "md")

// Load an image from the bundle, which can be localized.
let image = UIImage(named: "Sign", in: .module, with: nil)

// Get localization out of strings files.
var localizedGreeting = NSLocalizedString("greeting", bundle: .module)

12.Swift Package Manager 二进制依赖

定义为 binary target

extension Target {
    /// Declare a binary target with the given url.
    public static func binaryTarget(
        name: String,
        url: String,
        checksum: String
    ) -> Target

    /// Declare a binary target with the given path on disk.
    public static func binaryTarget(
        name: String,
        path: String
    ) -> Target
}

使用: .product(name: "MyBinaryLib", type: .static, targets: ["MyBinaryLib"])

13.Swift Package Manager 条件依赖

// swift-tools-version:5.3

import PackageDescription

let package = Package(
    name: "BestPackage",
    dependencies: [
        .package(url: "https://github.com/pureswift/bluetooth", .branch("master")),
        .package(url: "https://github.com/pureswift/bluetoothlinux", .branch("master")),
    ],
    targets: [
        .target(
            name: "BestExecutable",
            dependencies: [
                .product(name: "Bluetooth", condition: .when(platforms: [.macOS])),
                .product(name: "BluetoothLinux", condition: .when(platforms: [.linux])),
                .target(name: "DebugHelpers", condition: .when(configuration: .debug)),
            ]
        ),
        .target(name: "DebugHelpers")
     ]
)

14.Unowned Optional References 可选的 Unowned

被 unowned 标记后代表,这个对象不会走引用计数和 weak 表,需要手动维护 unowned 对象的内存指向,Swift 5.3 添加了可选 unowned,就是定义了unowend 对象后,你可以给他赋值 nil,可选对象默认为 nil,访问不会报错也会走 optional 的逻辑,但如果 unowned 对象被赋值,赋值之后指向的内存被释放,再次访问就会 bad access, unowned 不会自动赋值为 nil。
所以虽然是 optional,但一定要维护好指向的对象的释放时间。

class Department {
    var name: String
    var courses: [Course]
    init(name: String) {
        self.name = name
        self.courses = []
    }
}

class Course {
    var name: String
    unowned var department: Department
    unowned var nextCourse: Course?
    init(name: String, in department: Department) {
        self.name = name
        self.department = department
        self.nextCourse = nil
    }
}

let c1 = Course.init(name: "c1", in: .init(name: "asdfasd"))
print(c1.nextCourse)
autoreleasepool {
    let c2 = Course.init(name: "c2", in: .init(name: "asdfasd"))
    c1.nextCourse = c2
    print(c1.nextCourse)
    c1.nextCourse = nil
    print(c1.nextCourse)
    c1.nextCourse = c2
}

print(c1.nextCourse)

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