记录 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
的使用
- 可以把
[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
}
}
- 如果
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
- 题外话, 值类型如何在逃逸闭包里面修改自己的属性, 智障
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
类型
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 之后相同
其他还有为一些脚本使用
- 桌面新建一个叫 Command.swift 的 swift 文件
@main
struct Command {
static func main() {
print("run")
}
}
- cmd 在桌面文件夹下
swiftc -parse-as-library Command.swift
得到可执行文件, 双击, 得到
/Users/xxxx/Desktop/Command ; exit;
run
- 如果报错, 建议新建一个文件夹, 在这个文件夹下操作, 因为 build 的时候, 会查找当前文件夹和子文件夹, 如果存在多个 @main 就会报错
- 使用 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,多语言
- 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)
- 多语言
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