Swift 6 新特性

96 阅读4分钟

默认启用完整并发

提案地址 [SE-0414]

在 Swift 6 之前,设置 Xcode-Build Settings- Strict Concurrency Checking 为 Complete,启用完整并发检查

调用 loadData() 会抛出一个警告:

passing argument of non-sendable type 'User' outside of main actor-isolated context may introduce data races.

意思就是在主隔离上下文以外传递不可发送参数,可能会导致数据竞争

在 Swift 6 之后,这个警告消失了:Swift 现在检测到代码实际上没有问题,因为 user 并没有被同时访问,因此不会发出警告。

全局变量的并发安全

提案地址 [SE-0412]

要求全局变量和类型中的静态变量在并发环境中是安全的

var test = 0

struct Test1 {
    static var test = true
}

struct Test2 {
    static let test = 9.975
}

以上代码在 Swift6 之前打开完整并发检查会警告 test 和 Test1.test,在 Swift6 中是错误

Var 'test' is not concurrency-safe because it is non-isolated global shared mutable state; this is an error in Swift 6

我们可以通过改成 let 修饰 或者添加 @MainActor 隔离在主线程调用

还有一种方法可以解除报错,标记为非隔离的

 // 不推荐,除非你确定它是安全的
nonisolated(unsafe) var test = 0

函数默认值的隔离

提案地址 [SE-0411]

以下代码在 Swift6 之前会报错(未开启完整并发检查的情况下),在 Swift6 之后正常编译

@MainActor
class Logger {

}

@MainActor 
class DataController {
    init(logger: Logger = Logger()) {

    }
}

// error
Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context

新增 count(where:)

提案地址 [SE-0220]

引入了一个新的 count(where:) 方法,它相当于先 filter()count 的操作。直接使用这个方法可以少创建一个新数组

// 2 步操作
[1, 2, 3, -1, -2].filter({ $0 > 0 }).count // => 3
// 1 步到位
[1, 2, 3, -1, -2].count(where: { $0 > 0 }) // => 3

PR 实现

@inlinable
  public func count(
    where predicate: (Element) throws -> Bool
  ) rethrows -> Int {
    var count = 0
    for e in self {
      if try predicate(e) {
        count += 1
      }
    }
    return count
  }
}

类型化的 throws

提案地址 [SE-0413]

enum TestError: Error {
  case test1, test2
}

// Swift 5.x
func testError1(_ num: Int) throws {
  if num > 5 {
    throw TestError.test1
  }
}

// Swift 6
func testError2(_ num: Int) throws(TestError) {
  if num > 5 {
    throw .test2
  }
}

// 调用
    do {
      try testError1(10)
    } catch {
      debugPrint(error)
    }

在 Swift6 中如果指定了抛出的 error 类型,那么不可在抛出其他类型的 error

Pack(参数包) 迭代

提案地址 [SE-0408]

Swift 5.9 提案地址 [SE-0393] 中引入的参数包功能的能力,可以说是 Swift 中很复杂的功能之一

比如比较元组是否相等

struct NotEqual: Error {}

func == <each Element: Equatable>(lhs: (repeat each Element), rhs: (repeat each Element)) -> Bool {
  // Local throwing function for operating over each element of a pack expansion.
  func isEqual<T: Equatable>(_ left: T, _ right: T) throws {
    if left == right {
      return
    }
    
    throw NotEqual()
  }

  // Do-catch statement for short-circuiting as soon as two tuple elements are not equal.
  do {
    repeat try isEqual(each lhs, each rhs)
  } catch {
    return false
  }

  return true
}


    // 定义元组,每个元组包含三个元素
    let tuple1 = (1, "hello", 3.14)
    let tuple2 = (1, "hello", 3.14)
    let tuple3 = (2, "world", 2.71)

    // 使用我们定义的相等操作符进行比较
    let isEqual1 = tuple1 == tuple2 // 这将返回 true,因为两个元组的所有元素都相等
    let isEqual2 = tuple1 == tuple3 // 这将返回 false,因为元组的元素不完全相同

    print(isEqual1) // 输出: true
    print(isEqual2) // 输出: false

Swift 6 新的提案优化了 pack 技术,可以使用for-in 循环对值包进行迭代

func == <each Element: Equatable>(lhs: (repeat each Element), rhs: (repeat each Element)) -> Bool {

  for (left, right) in repeat (each lhs, each rhs) {
    guard left == right else { return false }
  }
  return true
}

非连续元素的集合操作

提案地址 [SE-0270]

我们可以使用 Range<Index>来引用集合中的一组连续位置,但标准库目前不提供引用任意集合中不连续位置的方法,所以有了这么一个提案 RangeSet<Bound: Comparable>

var numbers = Array(1...15)

// Find the indices of all the even numbers
let indicesOfEvens = numbers.indices(where: { $0.isMultiple(of: 2) })

// Perform an operation with just the even numbers
let sumOfEvens = numbers[indicesOfEvens].reduce(0, +)
// sumOfEvens == 56

// You can gather the even numbers at the beginning
let rangeOfEvens = numbers.moveSubranges(indicesOfEvens, to: numbers.startIndex)
debugPrint(numbers)
debugPrint(numbers[rangeOfEvens])
debugPrint(numbers[indicesOfEvens])

导入声明的访问级别修饰符

提案地址 [SE-0409]

增加了为导入声明标记访问控制修饰符的能力。

比如以后导入头文件你可以这么写了 internal import SomeLibrary。作用是 SomeLibrary 这个库只会在你的库内部使用,外部引入你的库的 App 看不到这个依赖库。

这将对开发 SDK 的团队避免意外泄露其依赖关系非常有用。

不可复制类型的升级

提案地址 [SE-0427]

不可复制类型是 Swift5.9 引入的 提案地址 [SE-0390],但是不支持泛型、协议,Swift6 支持了 结构体、枚举、类、泛型参数、协议和关联类型

struct Test: ~Copyable {

  consuming func test() {
    debugPrint("test")
  }

  deinit {
    debugPrint("deinit")
  }
}

    let test = Test()
    test.test()
    // 放开注释就会报错 'test' consumed more than once,因为已经被消耗了
//    test.test()

128 位整数类型

提案地址 [SE-0425]

let num: Int128 = 170_141_183_460_469_231_731_687_303_715_884_105_727

函数体宏

提案地址 [SE-0415]

@Logged
func g(a: Int, b: Int) -> Int {
  return a + b
}


func g(a: Int, b: Int) -> Int {
  log("Entering g(a: (a), b: (b))")
  defer {
    log("Exiting g")
  }
  return a + b
}

Swift 回溯 API

提案地址 [SE-0419]