Initialiser functions in actors?

How do I pull the async stream initialiser code OUT of the actor initialiser so I don't get all the errors "Call to actor-isolated instance method"?

Here's the code I am simply trying to refactor out to make the init more readable... fighting the new actor model.

init() {
        recogniser = SFSpeechRecognizer()
        guard recogniser != nil else {
            //transcript
            return
        }
        
        configureTextUpdateContinuation()
        
        Task {
            do {
                guard await SFSpeechRecognizer.hasAuthorizationToRecognize() else {
                    throw RecognizerError.notAuthorizedToRecognize
                }
                
                guard await AVAudioSession.sharedInstance().hasPermissionToRecord() else {
                    throw RecognizerError.notPermittedToRecord
                }
            } catch {
                transcribe(error)
            }
        }
    }
    
    private func configureTextUpdateContinuation() {
        var localContinuation: AsyncStream<String>.Continuation?
        textUpdates = AsyncStream { continuation in
            localContinuation = continuation
        }
        
        textUpdateContinuation = localContinuation

    }

Have tried numerous things, but all are ugly and make the task of reading the code even worse.

Why has it become so much harder to simply refactor this code...

I tried nonisolated(unsafe) still falls apart.

Does not answer your question, but you can simplify that code:

private func configureTextUpdateContinuation() {
   let u = AsyncStream<String>.makeStream ()
   self.textUpdates = u.stream
   self.textUpdateContinuation = u.continuation
}

Edit:
Also, instead of calling an instance method:

initialisation of those two variables can be done directly in the init:

init() {
   ...        
   let u = AsyncStream<String>.makeStream ()
   self.textUpdates = u.stream
   self.textUpdateContinuation = u.continuation
   ...

I think this is actually not (only?) a problem with the concurrency model, but with definite initialization.

configureTextUpdateContinuation is an instance method, so it can only be called after the instance is fully initialized. i.e. all properties have been set.
One way is to make the relevant properties optional, that implicitly sets them to nil, so basically they're set twice then (no idea whether the compiler will optimize this out in the end). Of course that still requires all other properties to be set before the method call.
However, I personally dislike doing this if possible, as it changes my type's interface (even if it's only private properties) and can make things ugly.

The other way I sometimes use to make "creation code" reusable from elsewhere in my app is simply using (private) static functions that return the relevant things as a tuple or perhaps even a nested helper struct. I then set the properties with the result of those in init and wherever else I need.
Note that I said "wherever else" there, if it's really only in the initializer I usually do not do that. Even if the initializer becomes a little longer (a sign that perhaps the type is doing too much work anyway?), since definite initialization is important I don't consider splitting it up into various methods better readability per se...

That being said, I would fire that Task off in an extra method called at the end of init. It's potentially running concurrently to the initialization anyway.

2 Likes

Couldn’t that be a nuances with isolated actors initializers? Cannot remember actual proposal that covers this, but perhaps making init() async should help — as it then would be called in actor isolation and therefore allow to run other actor work.

Thank you, @Gero!

That caused a brain spark, and I updated my post.

1 Like