Home > Net >  How to filter an Actor object?
How to filter an Actor object?

Time:01-23

I have an Actor object that I want to be able to iterate and filter.

actor DataModel {
    typealias Details = (passed: Bool, scores: [Int])
    private(set) var data: [Int: Details] = [:]

    func update(_ value: (Bool, [Int]), forKey key: Int) {
        data.updateValue(value, forKey: key)
    }

    subscript(id: Int) -> Details? {
        get {
            data[id]
        }

        set {
            data[id] = newValue
        }
    }

    func removeAll() {
        data.removeAll()
    }
}

extension DataModel: AsyncSequence, AsyncIteratorProtocol {
    typealias Element = (key: Int, value: Details)

    func next() async throws -> Element? {
        var iterator = data.makeIterator()
        return iterator.next()
    }

    nonisolated func makeAsyncIterator() -> Data {
        self
    }
}

let data = DataModel()
await data.update((false, [1, 2]), forKey: 0)

But, whenever I use the filter method, it goes into an infinite loop.

let filtered = data.filter { el in
    /// infinite loop
    return el.value.passed || el.value.scores.count > 3
}

for try await i in filtered {
    print(i)
}

Update

Created a separate iterator, but getting the following error:

Actor-isolated property 'data' can not be referenced from a non-isolated context

extension DataDetail: AsyncSequence {
    typealias Element = (key: Int, value: (passed: Bool, scores: [Int]))
    typealias AsyncIterator = DataInterator

    nonisolated func makeAsyncIterator() -> DataInterator {
        return DataInterator(data) /// Actor-isolated property 'data' can not be referenced from a non-isolated context
    }
}

struct DataInterator: AsyncIteratorProtocol {
    typealias Detail = (key: Int, value: (passed: Bool, scores: [Int]))

    private let details: [Int: (passed: Bool, scores: [Int])]
    lazy var iterator = details.makeIterator()
    
    init(_ details: [Int: (passed: Bool, scores: [Int])]) {
        self.details = details
    }
    
    mutating func next() async throws -> Detail? {
        let nextDetail = iterator.next()
        return nextDetail
    }
}

CodePudding user response:

You have a mistake in your next() method. You're creating a new iterator on each call, so every call to your next() method is effectively returning data.first over and over again. It'll never hit nil, so it'll never end.

I'm not sure what the easiest way to fix it is, however. You can't just return data.makeIterator() from makeAsyncIterator(), because data is actor-isolated.

You'll probably want to make a new AsyncIteratorProtocol-conforming struct which wraps your actor and vends the elements of its data in an actor-isolated way

  • Related