0

How can I observe changes on an array with Combine framework but without SwiftUI?

Basically, I want this sink block to get called when an element is added or removed.

import Combine

var list = ["A", "B", "C"]
list.publisher
    .collect()
    .sink { value in
        print(value)
    }

// I want to observe these changes.
list.append("D")
list.removeAll { $0 == "B"}

I read this article and I know @Published property wrapper works this way. But I can't use SwiftUI and am looking for another solution.

taichino
  • 1,135
  • 1
  • 15
  • 30
  • Look up `AnyCancellable` and `AnyPublisher`. BTW `@Published` works with SwiftUI but it is a `Combine` wrapper. – lorem ipsum May 13 '22 at 19:44
  • Does that mean I need to implement my own publisher for this? – taichino May 13 '22 at 20:01
  • You need to store instance of `AnyCancellable`. Subscriptions return an instance of `AnyCancellable` as a **“cancellation token,”** .In above example your `subscription` will be `cancelled` automatically after *collecting* all values, and ignoring other commands like append. Check https://stackoverflow.com/questions/63543450/what-is-the-reason-to-store-subscription-into-a-subscriptions-set – Tushar Sharma May 13 '22 at 20:49
  • @TusharSharma Thank you for your comment! But I'm not sure if I understand you correctly. I think the issue is the publisher is completed before I start mutating the list. Created a gist as formatting doesn't seem to work in comment... https://gist.github.com/taichino/479ae77c15bd68ca6fbd871fe4badb30 – taichino May 13 '22 at 21:24

1 Answers1

2

Combine is a completely separate framework from SwiftUI, so it can be used on its own.

I would add a publisher for the list by using the @Published attribute. You can then receive events off of that.

You'll need to have the resulting AnyCancellable stored (such as in a set) so that the subscription can be torn down properly and doesn't deallocate immediately.

class MyClass {
    @Published var list: [String] = []
    
    private var cancellables: Set<AnyCancellable> = []
    
    func setUp() {
        $list
            .receive(on: RunLoop.main)
            .sink { listItems in
                // Items changed
            }.store(in: &cancellables)
    }
}

Drew
  • 674
  • 5
  • 14