Gernot
(Gernot Poetsch)
1
Hi all,
I have an issue I could not find any solution for. I have two @MainActor classes that form a hierarchy, so they reference each other, the back reference is a weak var. Now I want to have a debugDescription that has to be in a nonisolated context. When bar is in foo I want it to return "bar in foo" as its debug description.
I can't make the weak var into a let for obvious reasons, and I can't make a stored property non-isolated. I can also not use non isolated(unsafe) as that has been removed. So what can I do to make this work?
import Foundation
import SwiftUI
import PlaygroundSupport
@MainActor
class Foo {
let title = "Foo"
var bar: Bar?
func addBar(_ bar: Bar) {
self.bar = bar
bar.foo = self
}
}
@MainActor
class Bar: CustomDebugStringConvertible {
let title = "Bar"
weak var foo: Foo? // <- This can't be nonisolated since it's a stored variable, and it can't be made "let" because it needs to be weak.
nonisolated var debugDescription: String { // <- This has to be nonisolated for protocol conformance.
if let foo = foo { //Error: Property 'foo' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context
return "\(title) in \(foo.title)"
} else {
return title
}
}
}
Task {
await MainActor.run {
let foo = Foo()
let bar = Bar()
foo.addBar(bar)
}
}
Thanks,
Gernot.
michelf
(Michel Fortin)
2
Not sure, but maybe unowned let would work?
(As long as you don't rely on foo auto-nulling itself.)
Gernot
(Gernot Poetsch)
3
Same thing: error: nonisolated' can not be applied to stored properties
Gernot
(Gernot Poetsch)
4
I have found a way by putting a non-actor class with a weak var in a let and accessing its property in a non-isolated way. I works, but I think this is ugly and way too much boilerplate.
import Foundation
import SwiftUI
import PlaygroundSupport
@MainActor
class Foo {
let title = "Foo"
var bar: Bar?
func addBar(_ bar: Bar) {
self.bar = bar
bar.foo = self
}
}
@MainActor
class Bar: CustomDebugStringConvertible {
init() {
fooContainer = WeakContainer<Foo>(wrappedValue: nil)
}
let title = "Bar"
let fooContainer: WeakContainer<Foo>
nonisolated var foo: Foo? {
get { fooContainer.wrappedValue }
set { fooContainer.wrappedValue = newValue }
}
nonisolated var debugDescription: String {
if let foo = foo {
return "\(title) in \(foo.title)"
} else {
return title
}
}
}
class WeakContainer<Value: AnyObject> {
init(wrappedValue: Value?) {
self.wrappedValue = wrappedValue
}
weak var wrappedValue: Value?
}
Task {
await MainActor.run {
let foo = Foo()
let bar = Bar()
foo.addBar(bar)
bar.debugDescription
}
}
Gernot
(Gernot Poetsch)
5
Oh, correction: unowned let works well here! Since in the non-example code the hierarchy is set up in the init, there shouldn't be any downsides. Thanks!