我正在努力使一个班级达到Sendable人.我有一些可变的存储属性,这会导致问题.然而,我不能理解的是,MainActor隔离属性不允许我的类符合Sendable.然而,如果我给全班打@MainActor分,那就没问题了.然而,我实际上并不想让整个班级都符合@MainActor.

以下面的代码为例:

final class Article: Sendable {
  @MainActor var text: String = "test"
}

它给出了这样的警告:Stored property 'text' of 'Sendable'-conforming class 'Article' is mutable.

有人能解释一下为什么吗?我以为和一个演员隔绝一下就可以了.

推荐答案

该错误警告您,您的类公开了一个可变属性.该可变属性可以从SWIFT并发的外部访问,因此不安全.

请考虑以下事项:

final class Foo: Sendable {
    @MainActor var counter = 0   // Stored property 'counter' of 'Sendable'-conforming class 'Foo' is mutable
}

无论如何,我们现在可以考虑与counter直接交互的视图控制器的以下属性和方法:

let foo = Foo()

func incrementFooManyTimes() {
    DispatchQueue.global().async { [self] in
        DispatchQueue.concurrentPerform(iterations: 10_000_000) { _ in
            foo.counter += 1
        }
        print(foo.counter)   // 6146264 !!!
    }
}

注:如果您已将"SWIFT并发判断"版本设置设置为"Minimal"或"Targeted",则编译上述代码时将只显示上述警告.(如果您将其更改为"Complete",它将变成一个硬错误.)

总之,简而言之,如果您已经将标记为@MainActor,但没有任何东西可以阻止其他线程直接与该类的这个属性交互.


如果您要让一个具有可变属性的非参与者BESendable,您必须自己实现线程安全.例如:

final class Foo: @unchecked Sendable {
    private var _counter = 0
    private let queue: DispatchQueue = .main    // I would use `DispatchQueue(label: "Foo.sync")`, but just illustrating the idea

    var counter: Int { queue.sync { _counter } }

    func increment() {
        queue.sync { _counter += 1 }
    }
}

func incrementFooManyTimes() {
    DispatchQueue.global().async { [self] in
        DispatchQueue.concurrentPerform(iterations: 10_000_000) { _ in
            foo.increment()
        }
        print(foo.counter)   // 10000000
    }
}

显然,您也可以将自己限制为不可变的属性,而不需要同步.但我想你需要变化无常.

Now, in this mutable scenario, you can use whatever synchronization mechanism you want, but hopefully this illustrates the idea. In short, if you are going to allow it to mutate outside of Swift concurrency, you have to implement the synchronization yourself. 和 because we are implementing our own synchronization, we tell the compiler that it is @unchecked, meaning that you are not going to have the compiler check it for correctness, but rather that burden falls on your shoulders.


显然,如果你使用一个演员,并保持在SWIFT并发的世界中,生活就会容易得多.例如:

actor Bar {
    var counter = 0

    func increment() {
        counter += 1
    }
}

和:

let bar = Bar()

func incrementBarManyTimes() {
    Task.detached {
        await withTaskGroup(of: Void.self) { group in
            for _ in 0 ..< 10_000_000 {
                group.addTask { await self.bar.increment() }
            }
            await print(self.bar.counter)
        }
    }
}

Swift相关问答推荐

Swift Tree实现中的弱var

WWDC Swift并发会话中的厨房服务示例令人困惑

操作员如何点逻辑非(.!)在Swift工作?

如何让CTFontCreateUIFontForLanguage与汉字和表情包协同工作?

SWIFT并发:合并Taskgroup和AsyncStream?

Variadic泛型与Swift中的值和类型参数包

不能将符合协议的SWIFT类的实例分配给需要该协议的Objective-C属性

从SwiftUI使用UIPageView时,同一页面连续显示两次的问题

使用 @resultBuilder 的通用 buildList 函数

SwiftUI,如何更改具有多个按钮和一个布尔条件的一个按钮标签

TimeZone 背后的目的

如何在不提交到应用store 的情况下在本地运行我的应用

任何使 ScrollView 像 List 一样执行更灵活的修饰符?

通用枚举菜单 SwiftUI

Subclass.fetchRequest() Swift 3.0,扩展并没有真正帮助 100%?

FirebaseStorage:如何删除目录

如何使用 Swift/iOS 为人类书写的笔画制作动画?

Swift 中惰性 var 的优势是什么

如何使用 Swift 枚举作为字典键? (符合 Equatable)

转换为 swift 3 后,视图控制器中出现奇怪的通用函数