如何为 XCTest、SwiftUI 预览等初始化注释为 @MainActor 的 Swift 类

Nic*_*ner 6 ios async-await swift swiftui mainactor

我们希望在现有的 SwiftUI 项目中使用@MainActorViewModel 的注释,这样我们就可以摆脱DispatchQueue.main.async.receive(on: RunLoop.main)

@MainActor
class MyViewModel: ObservableObject {
    private var counter: Int
    init(counter: Int) {
        self.counter = counter
    }
}
Run Code Online (Sandbox Code Playgroud)

从 SwiftUI 视图初始化带注释的类时,这种方法效果很好。但是,当使用 SwiftUI Previews 或 XCTest 时,我们还需要从上下文外部初始化该类@MainActor

class MyViewModelTests: XCTestCase {

    private var myViewModel: MyViewModel!
    
    override func setUp() {
        myViewModel = MyViewModel(counter: 0)
    }
Run Code Online (Sandbox Code Playgroud)

这显然不能编译:

主要参与者隔离属性“init(counter:Int)”无法从非隔离上下文中进行变异

现在,显然我们也可以按照此处的建议MyViewModelTests进行注释。@MainActor

但我们不希望所有的单元测试都在主线程上运行。那么在这种情况下推荐的做法是什么?

如果我们不想在初始化器中设置变量的值,则按照上面对话中建议的init方式注释函数才有效。nonisolated

use*_*037 6

方法:

  • 你可以override func setUp() async throws使用

模型:

@MainActor
class MyViewModel: ObservableObject {
    var counter: Int
    
    init(counter: Int) {
        self.counter = counter
    }
    
    func set(counter: Int) {
        self.counter = counter
    }
}

Run Code Online (Sandbox Code Playgroud)

测试用例:

import XCTest
@testable import Demo

final class MyViewModelTests: XCTestCase {
    private var myViewModel: MyViewModel!
    
    override func setUp() async throws {
        myViewModel = await MyViewModel(counter: 10)
    }
    
    override func tearDown() async throws {
        myViewModel = nil
    }

    func testExample() async throws {
        await myViewModel.set(counter: 20)
    }
}
Run Code Online (Sandbox Code Playgroud)


SPa*_*tel 4

只需标记setUp()为@MainActor

class MyViewModelTests: XCTestCase {
    private var myViewModel: MyViewModel!

    @MainActor override func setUp() {
        myViewModel = MyViewModel(counter: 0)
    }
}
Run Code Online (Sandbox Code Playgroud)