Actor isolation rules different between async/sync funcs on a subclass of AVPlayer?

I have a created a class that is a subclass of AVPlayer, named MyPlayer.

AVPlayer is defined as having @MainActor isolation. It is my understanding that subclasses inherit the same actor isolation is their superclass. So, in my case MyPlayer should be isolated to the main-thread as well.

I have a separate Swift class (not Obj-C based) named PlayerManager. It has a reference to MyPlayer as a property of the class. It is not actor-isolated.

So, my code looks like this:

class MyPlayer: AVPlayer {
    var specialTimeRanges: [CMTimeRange] {
        currentItem?.seekableTimeRanges.compactMap { $0.timeRangeValue } ?? []
}

class PlayerManager {
    private let myPlayer: MyPlayer
     ....

    private func doSomethingWithTime() {
        let x = myPlayer.specialTimeRanges
        ....
    }
}

The problem that I have is that there are no warnings generated inside of doSomethingWithTime, and I don't know why.

I would've assumed that MyPlayer.specialTimeRangeswould be isolated to @MainActor, because it's superclass is. Then inside of PlayerManager, which is not isolated, I'd expect to see at least a warning about calling isolated code from a non-isolated context. But I'm not seeing that.

Next up, I tried making doSomethingWithTime an async func, and I do get the warning.

So, I'm not sure what's going on here. In the synchronous func, no warning. Async func, warning. The class is not actor-isolated.

So, either the compiler is not warning me about a possible actor-isolation issue OR I'm just missing something, and the code is OK. I mean, the "sync" case is acting like the function is going to run on the main-thread, but the func is not annotated as such, and neither is the class itself.

What am I missing?

Could be a bug, but practically speaking does labelling class MyPlayer with main actor annotation fix the issue for you?

OK, I think I see the problem. My subclass, MyPlayer... as far as inheritance goes, only the things that I inherit are @MainActor isolated, but if I add in new funcs & properties to the subclass, then those are nonisolated?

OK, that wasn't correct. I forgot to check what version of Swift this project was on. :man_facepalming: and it was less than 6.0

1 Like

Could you share a reproducer in Godbolt?

yeha, so many knobs to tune these days... Swift version, Approachable concurrency, Default actor isolation, Swift concurrency checking...

But there's no AVFoundation there, and probably no way to mock a a true objc class (in an .m file available for Swift).

I found that there could be differences, e.g. this:

import AVFoundation

@MainActor class MyAVPlayer: NSObject {}

class MyPlayer: MyAVPlayer {
    func foo() {}
}

actor A {
    func test(_ player: MyPlayer) {
        player.foo()
    }
}

gives an error "Call to main actor-isolated instance method 'foo()' in a synchronous actor-isolated context" while if I subclass from a real AVFoundation AVPlayer - it's a warning.

Do you think this is a bug in Obj-C interop? I tried to reproduce it in Godbolt, but to no avail. If I understand correctly, the issue is the following:

@MainActor 
class A {}

class B: A {
    var message = "Hello, World!"
}

nonisolated 
class C {
    let b = B()

    func f() {
        _ = b.message // No diagnostic here
    }
}

I'm pretty sure this is expected behavior with a @preconcurency type, which this would be. You might even be able to reproduce it within a single module if you make A both @MainActor and @preconcurrency but I'm not 100% sure.

Doing this in the same module requires being really careful about what other concurrency annotations you use, so that explicit nonisolated might get in the way.

Not entirely sure myself, but when I tested it, the diagnostic in both Swift 5 and 6 was only downgraded to a warning, which is what I expected. I might be overlooking something, though. However, it does seem weird that the diagnostic appears when adding async.