Closed1
Xcode 16 beta6で遭遇したXCTestの初期化失敗エラーを回避
Xcode 16 beta6でコンパイラの処理が変わったのか、beta4では通ってた処理が通らなくなった。
@MainActor MyTests: XCTestCase
のようにして、@MainActor
を付与したオブジェクトのテストを行なっていたが、それらのsetupWithError
での初期化が全部隔離されたアクターコンテキスト外からのアクセスとしてコンパイルエラーになった。
@MainActor MyTests: XCTestCase {
var object: MainActorObject!
override func setUpWithError() throws {
object = Object(...) // ❌ Call to main actor-isolated initializer 'init()' in a synchronous nonisolated context. Main actor-isolated property 'object' can not be mutated from a nonisolated context.
}
override func tearDownWithError() throws {
object.reset() // ❌ Main actor-isolated property 'object' can not be mutated from a nonisolated context.
}
}
手元で試したところ、下記のような継承関係の場合、サブクラスのeat()
はnonisolated
なままであり、継承先では全てのシンボルがアタッチしたグローバルアクターに隔離されるわけではなく、継承元にnonisolated
なメソッドがあった場合、そのまま引き継ぐ挙動になっていることが分かった。
class MyObject {
func eat() { }
}
@MainActor
class SubObject: MyObject {
override func eat() {}
func speak() { }
}
ただ気になるのは、Xcode16 beta4までは何の問題なく動いていたこと。
この継承の仕様が今入ったのか、もしくはbeta4の挙動が不正であり、バグとして修正されたのかどうなのか。
どのみち、setUpとtearDownをoverrideでアクター隔離する方法がないXCTestCaseはSwift Concurrencyとだいぶ相性が悪いと思う。Swift Testingならアクター隔離もお手のものなので、まったく問題にならない。
いまとりあえず全部処理を移行するのは大変なので、ワークアラウンドの処理を書いた。こんな感じで、全部MainActor.assumeIsolated
内で記述しちゃう。
@MainActor MyTests: XCTestCase, @unchecked Sendable {
var object: MainActorObject!
override func setUpWithError() throws {
MainActor.assumeIsolated {
object = Object(...) // ✅ OK
}
}
override func tearDownWithError() throws {
MainActor.assumeIsolated {
object.reset() // ✅ OK
}
}
}
ワークアラウンドではない正式な解決法としては、やはりSwift Testingに移行してアクター隔離されたinit
内で初期化を行うことだと思う。でもこうなってくると、XCTestの存在価値が一気になくなったようにも思う。
このスクラップは2024/08/24にクローズされました