Actor Isolation and weak vars

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.

Not sure, but maybe unowned let would work?

(As long as you don't rely on foo auto-nulling itself.)

Same thing: error: nonisolated' can not be applied to stored properties

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
    }
}

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!

Terms of Service

Privacy Policy

Cookie Policy