You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中@Entry与Actor隔离初始化器报错解决方案咨询

SwiftUI中DataAccess层Actor隔离错误的解决方法

问题描述

我在SwiftUI中搭建数据访问层(Data Access Layer)时,@Entry行始终报错:“在同步非隔离上下文中调用主Actor隔离的初始化器'init(container:)'”。先后尝试了初始实现和更新后的代码,但Actor隔离相关问题仍未解决。

初始代码

import Foundation
import SwiftData

@MainActor
protocol DataAccess {
    func getBudgets() throws -> [BudgetPlain]
    func addBudget(name: String, limit: Double)
}


@Entry var dataAccess: DataAccess =  BudgetSwiftDataAccess()


@MainActor
class BudgetSwiftDataAccess: DataAccess {
    
    var container: ModelContainer
    var context: ModelContext
    
    init(container: ModelContainer = try! ModelContainer(for: Budget.self, configurations: ModelConfiguration(isStoredInMemoryOnly: false))) {
        self.container = container
        self.context = container.mainContext
    }
    
    func getBudgets() throws -> [BudgetPlain] {
        let budgets = try context.fetch(FetchDescriptor<Budget>())
        return budgets.map(BudgetPlain.init)
    }
    
    func addBudget(name: String, limit: Double) {
        let budget = Budget(name: name, limit: limit)
        context.insert(budget)
    }
    
}

更新后代码

extension EnvironmentValues {
    @Entry var dataAccess: DataAccess = BudgetSwiftDataAccess()
}

@MainActor
class BudgetSwiftDataAccess: DataAccess {
    
    var container: ModelContainer
    var context: ModelContext
    
    @MainActor
    init(container: ModelContainer = ModelContainer.default()) {
        self.container = container
        self.context = container.mainContext
    }
    
    func getBudgets() throws -> [BudgetPlain] {
        let budgets = try context.fetch(FetchDescriptor<Budget>())
        return budgets.map(BudgetPlain.init)
    }
    
    func addBudget(name: String, limit: Double) {
        let budget = Budget(name: name, limit: limit)
        context.insert(budget)
    }
    
}

问题原因

BudgetSwiftDataAccess的初始化器被标记为@MainActor隔离,但EnvironmentValues的@Entry属性默认在非隔离上下文完成初始化,导致跨Actor调用的安全错误。

解决方法

方式一:App入口处注入主Actor初始化的实例

将数据访问层实例的初始化逻辑放在主Actor上下文的App入口中,再通过环境变量注入视图层级:

import SwiftUI
import SwiftData

@MainActor
protocol DataAccess {
    func getBudgets() throws -> [BudgetPlain]
    func addBudget(name: String, limit: Double)
}

extension EnvironmentValues {
    @Entry var dataAccess: DataAccess! // 改为可选类型,延迟注入
}

@MainActor
class BudgetSwiftDataAccess: DataAccess {
    var container: ModelContainer
    var context: ModelContext
    
    init(container: ModelContainer) {
        self.container = container
        self.context = container.mainContext
    }
    
    convenience init() throws {
        let container = try ModelContainer(for: Budget.self)
        self.init(container: container)
    }
    
    func getBudgets() throws -> [BudgetPlain] {
        let budgets = try context.fetch(FetchDescriptor<Budget>())
        return budgets.map(BudgetPlain.init)
    }
    
    func addBudget(name: String, limit: Double) {
        let budget = Budget(name: name, limit: limit)
        context.insert(budget)
    }
}

// App入口统一初始化并注入
@main
struct YourApp: App {
    @State private var dataAccess: DataAccess
    
    init() {
        do {
            // 主Actor上下文中初始化实例
            self._dataAccess = State(wrappedValue: try BudgetSwiftDataAccess())
        } catch {
            fatalError("初始化数据访问层失败:\(error)")
        }
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.dataAccess, dataAccess)
        }
        .modelContainer(for: Budget.self)
    }
}

方式二:用主Actor闭包包装@Entry默认值

如果希望保留@Entry的默认值逻辑,可通过MainActor.assumeIsolated强制在主Actor上下文执行初始化:

import SwiftUI
import SwiftData

@MainActor
protocol DataAccess {
    func getBudgets() throws -> [BudgetPlain]
    func addBudget(name: String, limit: Double)
}

@MainActor
class BudgetSwiftDataAccess: DataAccess {
    var container: ModelContainer
    var context: ModelContext
    
    init(container: ModelContainer) {
        self.container = container
        self.context = container.mainContext
    }
    
    convenience init() throws {
        let container = try ModelContainer(for: Budget.self)
        self.init(container: container)
    }
    
    func getBudgets() throws -> [BudgetPlain] {
        let budgets = try context.fetch(FetchDescriptor<Budget>())
        return budgets.map(BudgetPlain.init)
    }
    
    func addBudget(name: String, limit: Double) {
        let budget = Budget(name: name, limit: limit)
        context.insert(budget)
    }
}

extension EnvironmentValues {
    @Entry var dataAccess: DataAccess {
        get { self[DataAccessKey.self] }
        set { self[DataAccessKey.self] = newValue }
    }
    
    private struct DataAccessKey: EnvironmentKey {
        static let defaultValue: DataAccess = {
            // 强制在主Actor上下文执行初始化
            MainActor.assumeIsolated {
                do {
                    return try BudgetSwiftDataAccess()
                } catch {
                    fatalError("创建数据访问层实例失败:\(error)")
                }
            }
        }()
    }
}

关键说明

  • 所有涉及ModelContainer、ModelContext的操作必须在主Actor上下文执行,避免跨线程访问SwiftData核心对象。
  • 推荐采用App入口注入的方式,能更清晰地控制实例初始化时机和上下文,降低后续维护复杂度。

内容的提问来源于stack exchange,提问作者john doe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 16:20:55