升级 Xcode 15 之后,你能用到哪些 swift 新特性?

这里每天分享一个 iOS 的新知识,快来关注我吧

前言

Xcode 15 正式版已经发布了,其中内置了 swift 5.9 版本,今天来罗列一下 swift 5.9 有哪些新特性。

1、新增 consume 运算符

consume 运算符可以结束局部变量的生命周期,举个例子:

scss 复制代码
let arr = [1, 2, 3]
consume arr
print(arr)

arr 被 consume 标记之后,明确告诉编译器,这个值在之后的代码中不允许再次使用,因此在第三行 print 的时候会报错:

'arr' used after consume

2、将 sleep(for:) 方法添加到 Clock 协议中

swift 复制代码
let clock: any Clock<Duration> = ContinuousClock()

try await clock.sleep(for: .seconds(2), tolerance: .seconds(5))

print("睡眠两秒之后执行")

3、增加参数所有权关键字 borrowing 和 consuming

borrowing 和 consuming 是主要是为不可复制类型提供的两个新属性。

swift 复制代码
func consume(_ number: consuming SomeType) {}
func borrow(_ number: borrowing SomeType) {}

调用被标记 consuming 的方法参数会被消费掉,有点像上边提到的 consume 关键字。

另一个属性 borrowing 表示该参数只是以只读模式借用外部值。可以简单的将其视为一个引用指针。

4、if 和 switch 表达式

这个在之前的文章中有详细介绍过:Swift 5.9 新增 if 和 switch 作为表达式的能力

允许下边这两种写法:

bash 复制代码
let x = if p { 0 } else { 1.0 }
csharp 复制代码
let y: Float = switch x.value {
    case 0..<0x80: 1
    case 0x80..<0x0800: 2.0
    case 0x0800..<0x1_0000: 3.0
    default: 4.5
}

5、新型的结构化并发任务组:DiscardingTaskGroup

DiscardingTaskGroup 将立即丢弃其子任务的结果并释放产生该结果的子任务。这允许高效且"永远运行"的请求接受循环,例如 HTTP 或 RPC 服务器。

csharp 复制代码
try await withThrowingDiscardingTaskGroup() { group in
    while let newConnection = try await listeningSocket.accept() {
        group.addTask {
            handleConnection(newConnection)
        }
    }
}

这与 iOS 13 出的 withThrowingTaskGroup 函数相比,不会泄漏任务,因此是安全的,是表达此类处理程序循环的推荐方法。

6、今年的重头戏 Swift Macros

关于 Swift 宏,我在之前的文章有单独详细介绍过,感兴趣的可以去看看。

完整介绍一下关于 Xcode 15 新出的宏

7、导入前向声明的 Objective-C 接口和协议

使用方法:

less 复制代码
// @class Foo turns into
@available(*, unavailable, message: "This Objective-C class has only been forward declared; import its owning module to use it")
class Foo : NSObject {}

// @protocol Bar turns into
@available(*, unavailable, message: "This Objective-C protocol has only been forward declared; import its owning module to use it")
protocol Bar : NSObjectProtocol {}

8、新的访问修饰符 package

目前,要访问另一个模块中的符号,需要声明该符号为 public。然而,符号 public 允许从任何模块访问它,无论是在包内还是从包外,这有时是不可取的。

package 修饰符标记的符号只能被包内的模块访问。

这在之前的文章中也有介绍过:swift 5.9 新增访问修饰符

9、AsyncStream 和 AsyncThrowingStream 上添加 makeStream 方法

这个变化引入用于创建 AsyncStreamAsyncThrowingStream 实例的辅助方法,这使得流的延续性更容易访问。

使用方法:

scss 复制代码
let (stream, continuation) = AsyncStream.makeStream(of: Int.self)

await withTaskGroup(of: Void.self) { group in
  group.addTask {
    for i in 0...9 {
      continuation.yield(i)
    }
    continuation.finish()
  }

  group.addTask {
    for await i in stream {
      print(i)
    }
  }
}

10、不可复制的结构体和枚举

默认情况下,我们创建的结构体和枚举都是可复制的,这意味着可以为该类型的任何值创建多个相同的、可互换的表示形式。然而,有时候我们需要不可复制的类型。

可以使用 ~Copyable 来约束结构体或枚举是否可以被复制:

arduino 复制代码
struct MyStruct: ~Copyable {
}

enum MyEnum: ~Copyable {
}

如果尝试复制,将会报错:

ini 复制代码
let mys = MyStruct()
let mys1 = mys // Error: Cannot consume noncopyable stored property 'mys' that is global

11、自定义 Actor 执行者

使用方法:

swift 复制代码
class SpecificThreadExecutor: SerialExecutor {
    let someThread: SomeThread // simplified handle to some specific thread
    
    func enqueue(_ job: consuming ExecutorJob) {
        let unownedJob = UnownedExecutorJob(job) // in order to escape it to the run{} closure
        someThread.run {
            unownedJob.runSynchronously(on: self)
        }
    }
    
    func asUnownedSerialExecutor() -> UnownedSerialExecutor {
        UnownedSerialExecutor(ordinary: self)
    }
}

extension SpecificThreadExecutor {
    static var sharedUnownedExecutor: UnownedSerialExecutor {
        // ... use some shared configured instance and return it ...
    }
}

actor Worker {
    nonisolated var unownedExecutor: UnownedSerialExecutor {
        // use the shared specific thread executor mentioned above.
        // alternatively, we can pass specific executors to this actors init() and store and use them this way.
        SpecificThreadExecutor.sharedUnownedExecutor
    }
}

12、值和类型参数包

泛型函数当前需要固定数量的类型参数。不可能编写无限数量具有不同类型的参数的泛型函数。

swift 5.9 添加的值和类型参数包,允许这么做了:

swift 复制代码
func test<each T>(_ p: repeat each T) {
    
}

// 可以传入无限个范型参数
test(1, "1", 1.0, [1, 2, 3])

13、swift Macro 支持 SPM

第三方提供的宏,可以通过 SPM 来引入项目。

14、Observation

Swift 中已经有 KVOObservableObject,但 KVO 只能与 NSObject 一起使用,而 ObservableObject 需要使用 Combine

新增的 Observation 适用于所有 Swift 引用类型,而不仅仅是继承自 NSObject 的引用类型,并且能够利用语言的优势跨平台工作诸如 async / await 之类的功能。

使用 @Observation 宏标记类,之后,这个类所有的存储属性都可被监听

kotlin 复制代码
@Observable class Car {
    var name: String
}

15、使 Never 符合 Codable 协议

标准库向 Never 类型遵守 EncodableDecodable 协议。

16、允许通过包抽象泛型

这个改变和前面提到的第 12 条相关,在泛型类型的泛型参数列表中, each 关键字声明泛型参数包,就像在泛型函数的泛型参数列表中一样。存储属性的类型可以包含包扩展类型。

使用方法:

swift 复制代码
struct ZipSequence<each S: Sequence>: Sequence {
  typealias Element = (repeat each S.Element)

  let seq: (repeat each S)

  func makeIterator() -> Iterator {
    return Iterator(iter: (repeat (each seq).makeIterator()))
  }

  struct Iterator: IteratorProtocol {
    typealias Element = (repeat each S.Element)

    var iter: (repeat each S.Iterator)

    mutating func next() -> Element? {
      return ...
    }
  }
}

17、元组值包的扩展

类型参数包仅允许在函数参数列表、元组元素或泛型参数列表中使用。这阻止将类型参数包声明为函数返回类型、类型别名或局部变量类型以允许存储。针对这些限制的可用解决方案是将值包包含在元组中。

使用方法:

swift 复制代码
func tuplify<each T>(_ value: repeat each T) -> (repeat each T) {
  return (repeat each value)
}

func example<each T>(_ value: repeat each T) {
  let abstractTuple = tuplify(repeat each value)
  repeat print(each abstractTuple) // okay as of this proposal

  let concreteTuple = (true, "two", 3)
  repeat print(each concreteTuple) // invalid

  let mixedConcreteAndAbstractTuple = (1, repeat each value)
  repeat print(each mixedConcreteAndAbstractTuple) // invalid

  let labeledAbstractTuple = (label: repeat each value)
  repeat print(each labeledAbstractTuple) // invalid
}

18、初始化访问器

支持在计算属性中访问初始化方法

swift 复制代码
struct Angle {
    var degrees: Double
    var radians: Double {
        @storageRestrictions(initializes: degrees)
        init(initialValue)  {
            degrees = initialValue * 180 / .pi
        }
        
        get { degrees * .pi / 180 }
        set { degrees = newValue * 180 / .pi }
    }
    
    init(degrees: Double) {
        self.degrees = degrees // initializes 'self.degrees' directly
    }
    
    init(radiansParam: Double) {
        self.radians = radiansParam // calls init accessor for 'self.radians', passing 'radiansParam' as the argument
    }
}

这里每天分享一个 iOS 的新知识,快来关注我吧

本文同步自微信公众号 "iOS新知",每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!

相关推荐
DisonTangor9 小时前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone
- 羊羊不超越 -9 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
2401_865854881 天前
iOS应用想要下载到手机上只能苹果签名吗?
后端·ios·iphone
HackerTom2 天前
iOS用rime且导入自制输入方案
ios·iphone·rime
良技漫谈2 天前
Rust移动开发:Rust在iOS端集成使用介绍
后端·程序人生·ios·rust·objective-c·swift
2401_852403552 天前
高效管理iPhone存储:苹果手机怎么删除相似照片
ios·智能手机·iphone
星际码仔2 天前
【动画图解】是怎样的方法,能被称作是 Flutter Widget 系统的核心?
android·flutter·ios
emperinter2 天前
WordCloudStudio:AI生成模版为您的文字云创意赋能 !
图像处理·人工智能·macos·ios·信息可视化·iphone
关键帧Keyframe2 天前
音视频面试题集锦第 7 期
音视频开发·视频编码·客户端
关键帧Keyframe2 天前
音视频面试题集锦第 8 期
ios·音视频开发·客户端