0

I'm having trouble working out how to access an actor from a filter closure. Take this example code:

import Foundation

actor MyActor {
    var contents: [String]
    
    init() {
        self.contents = []
    }
}


let myActor = MyActor()
let list: [String] = []

Task {
    list.filter { item in myActor.contents.contains { $0 == item } }
}

I get the syntax error: Actor-isolated property 'contents' can not be referenced from a non-isolated context. Which makes sense, but I just cannot work out how to provide the correct syntax.

Philip Pegden
  • 1,732
  • 1
  • 14
  • 33
  • What would it even mean though, suppose for example the actor's contents mutate while you are filtering your list? – Shadowrun May 23 '22 at 16:49

2 Answers2

1

Put the actor's contents in a local let constant first, then use that local constant in filter:

Task {
    let actorContents = await myActor.contents
    let result = list.filter { item in actorContents.contains { $0 == item } }
}

You wouldn't want filter to use the updated myActor.contents halfway through, if myActor.contents changes at some point during the filtering, right? Therefore, make a local copy of it first, and use that.

Also, consider using a Set for better efficiency:

let actorContents = Set(await myActor.contents)
let result = list.filter { item in actorContents.contains(item) }
Sweeper
  • 213,210
  • 22
  • 193
  • 313
0

Another approach is to move the filtering routine into the actor:

actor MyActor {
    var contents: Set<String> = []

    func filtering(_ items: [String]) -> [String] {
        items.filter { contents.contains($0) }
    }
}

Then, that simplifies the call point:

func foo() {
    Task {
        let filtered = await myActor.filtering(list)
    }
}

This moves the synchronization logic to the actor (where it belongs, IMHO).

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • in this example I agree. In a more general pattern, say where the actor is protecting a dictionary, the other method applies also. – Philip Pegden May 23 '22 at 17:33