Concurrency warning from async network call returning parsed json dictionary in actor method

I have a large project with mixed C++, Objective-C, and Swift. I'm starting to enable strict concurrency checking to iron out as many errors I can ahead of Swift 6's release, and I'm running into a warning about a network utility method I have in Objective-C that fetches data from a URL and returns the result as a JSON dictionary.

It looks like this:

+(void)fetchJsonFrom:(NSURL *)url completion:(void (^)(NSDictionary * _Nullable))completion
{
    [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSDictionary *parsed = nil;
        if (data)
            parsed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        
        if ([parsed isKindOfClass:[NSDictionary class]])
            completion(parsed);
        else
            completion(nil);
        }] resume];
}

I have an actor that calls the method:

actor Foo {
    func fetch() async {
        let url = URL(string: "https://www.olivetree.com")!
        let jsonData = await NetworkUtil.fetchJson(from: url)
        //do stuff with json
    }
}

I get this warning from the call to fetchJson:

Non-sendable type '[AnyHashable : Any]?' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary

I think I understand why this warning exists - the contents of this NSDictionary could be anything and it therefore can't be verified to be Sendable, so it can't safely cross the threading boundary. But in this case I think it actually is safe, because it's not being used by the fetchJson method after the call completes.

I've managed to solve this by having the fetchJson method return the Data / NSData directly and do the json parse in the caller, but that's a little awkward. And I should note that the reason it's in Objective-C is that in my actual project it calls through to some C++ network code, but I made a test app with exactly the code above and still got the warning.

Can someone point me to a better solution here?

There's a pitch up to solve this kind of problem: [Pitch] `transferring` isolation regions of parameter and result values

If that proposal is accepted, you'd be able to annotate that fetchJsonFrom returns a transferring result in the header, so that Swift understands the non-Sendable value can be treated as "disconnected", meaning that no other values are referencing it, so it's safe to pass over the isolation boundary.

2 Likes

That's great! Will there be a way to annotate fetchJson in Objective-C, even?

Yes, you should be able to write __attribute__((__swift_attr__("transferring"))) on the result type in Objective-C. Of course, the compiler can't validate that for you like it can from Swift.

1 Like

SE-0430 has been accepted and shipped, and I believe transferring got renamed to sending, and is available to Objective-C via NS_SWIFT_SENDING. But I can't get it to work when I apply it in various ways to the Objective-C method declaration, to mark the dictionary as sending. I've filed a compiler bug: