Sending 'self' risks causing data races

Hi! I'm trying to implement Swift 6 in my code but can't fix one problem. Here is my code example which could be run in playground:

import UIKit
import WatchConnectivity

public final class MulticastDelegate<T>: Sendable {
    nonisolated(unsafe) private var delegates = [WeakWrapper]()
    
    public init() { }
    
    public var isEmpty: Bool {
        return delegates.isEmpty
    }
    
    public func addDelegate(_ delegate: T) {
        let wrapper = WeakWrapper(value: delegate as AnyObject)
        delegates.append(wrapper)
    }
    
    public func removeDelegate(_ delegate: T) {
        delegates = delegates.filter { $0.value !== delegate as AnyObject }
    }
    
    public func invokeDelegates(_ invocation: (T) -> Void) {
        for (index, delegate) in delegates.enumerated().reversed() {
            if let delegate = delegate.value as? T {
                invocation(delegate)
            } else {
                delegates.remove(at: index)
            }
        }
    }
    
    public func invokeDelegatesCheckingResponse(_ invocation: (T) -> Bool) -> Bool {
        var isHandled = false
        for delegate in delegates {
            if let delegate = delegate.value as? T {
                if invocation(delegate) {
                    isHandled = true
                    break
                }
            }
        }
        return isHandled
    }
    
    private final class WeakWrapper: Sendable {
        nonisolated(unsafe) weak var value: AnyObject?
        
        init(value: AnyObject) {
            self.value = value
        }
    }
}

@globalActor public actor WatchActor {
    public static var shared = WatchActor()
}

@MainActor
@objc public protocol WatchCommunicatorDelegate: NSObjectProtocol {
    @objc optional func watchCommunicatorDidRequestDataUpdate(_ controller: WatchCommunicator)
}


@WatchActor
@objc public final class WatchCommunicator: NSObject {
    private let multicastDelegate = MulticastDelegate<WatchCommunicatorDelegate>()
}

extension WatchCommunicator: @preconcurrency WCSessionDelegate {
    public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: (any Error)?) {
        multicastDelegate.invokeDelegates { delegate in
            Task { @MainActor in
                delegate.watchCommunicatorDidRequestDataUpdate?(self)
            }
        }
    }
    
    public func sessionDidBecomeInactive(_ session: WCSession) {
   
    }
    
    public func sessionDidDeactivate(_ session: WCSession) {
     
    }
}

I want to work with WatchCommunicator in global actor and WatchCommunicatorDelegate should be call in main actor and should have reference to WatchCommunicator.

Help please

The actual error makes a lot of sense. In this context self is an instance of WatchCommunicator, which is bound to WatchActor, a custom custom global actor. However, watchCommunicatorDidRequestDataUpdate is a member of the WatchCommunicatorDelegate protocol, which is bound to the main actor. Thus, you’re trying to pass a non-sendable type between two different isolation domains, and that’s not allowed.

As to how you fix this, it’s hard to say without more context. To start, why is WatchCommunicatorDelegate bound to the main actor rather than WatchActor? Given that WatchCommunicator is bound to WatchActor, it kinda makes sense for the delegate to be the same.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

So, it is impossible if I want to have WatchCommunicator working in the background thread and delegate called in main thread with reference to instance from what it called?

Sending 'self' risks causing data races
 
 
Q