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 {
  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(value: nil) has not been set yet: file __lldb_expr_13/MyPlayground.playground, line 68
代码2:
@propertyWrapper
struct Delayed {
  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(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

你可能感兴趣的:(Swift 5.3)