swiftconcurrencyswift-concurrencyswift6

Swift6 concurrency and NSBundleResourceRequest


I am struggling to understand why my code doesn't work in Swift6 mode.

Here is a minimalistic example:

final actor ODRRequest {
    let request = NSBundleResourceRequest(tags: ["bla"])

    func startRequest() {
        Task {[unowned self] in
            let available = await request.conditionallyBeginAccessingResources()
            if available {
                updateState(.available)
            }
        }
    }
}

This fails telling me that "Sending actor-isolated value of type 'NSBundleResourceRequest' with later accesses to nonisolated context risks causing data races".

At the same time, this works without issues:

private func startRequest() {
    request.conditionallyBeginAccessingResources {[unowned self]  available in
        Task {
            if available {
                await updateState(.available)
            }
        }
    }
}

Can anyone explain to me why does compiler complain about the first implementation but does not complain about the second? These functions are supposed to be identical.


Solution

  • I think you can do what you're trying to do — i.e., move your request and the methods that talk to it off into a single instance — by using a simple class instead of an actor; here's a sketch:

    final class ODRRequest {
        var request: NSBundleResourceRequest?
    
        func startRequest() async {
            let request = NSBundleResourceRequest(tags: ["bla"])
            if await request.conditionallyBeginAccessingResources() {
                // do stuff
            }
            self.request = request
        }
    
        func beginAccessing() async throws {
            try await request?.beginAccessingResources()
            /// do stuff if we get here
        }
    
        func stopAccessing() {
            request?.endAccessingResources()
            request = nil
        }
    }
    

    That compiles on my machine and I don't think I'm cheating the compiler in some way. Note the use of an Optional; that's how I always do bundle resource requests, so that I can create the request when I'm ready to start using it, and release the request when I'm finished with it. (Either that or I hold my requests in a mutable dictionary, keyed by tags, but it amounts to the same thing.)