Elye
Elye

Reputation: 60251

Can Combine be used in struct (instead of class)?

When using Combine as below

var cancellables: [AnyCancellable] = []

func loadItems(tuple : (name : String, imageURL : URL)) {
    URLSession.shared.dataTaskPublisher(for: tuple.imageURL)
        .sink(
            receiveCompletion: {
                completion in
                switch completion {
                case .finished:
                    break
                case .failure( _):
                    return
                }},
            receiveValue: { data, _ in DispatchQueue.main.async { [weak self] in self?.displayFlag(data: data, title: tuple.name) } })
        .store(in: &cancellables)
}

We don't need to call cancel in the deinit as below

deinit {
    cancellables.forEach {
        $0.cancel()
    }
}

Given that in https://developer.apple.com/documentation/combine/anycancellable, it is stated:

An AnyCancellable instance automatically calls cancel() when deinitialized.

Given we don't need to release during deinit, can the Combine be used in struct instead of class?

Upvotes: 5

Views: 3622

Answers (2)

jjoelson
jjoelson

Reputation: 5961

To answer your question directly, AnyCancellable does not rely on being stored in a class in order to cancel itself. Like any ref-counted object, it can be stored in a struct just fine, and it will be properly de-initialized and thus cancelled when there are no more references to it.

That said, you are correct to be suspicious here. You probably don't want to store an AnyCancellable in a struct the way you are doing it here. For starters, you would have to mark your loadItems function as mutating to even get it to compile, because storing the AnyCancellable means mutating the cancellables array.

Typically, if you're storing an AnyCancellable then you are associating that operation with something that has true identity, and thus is better represented as a class. You are basically saying "cancel this operation when this instance goes away". For example, if you're downloading an image to display in a UIViewController, you probably want to cancel that download if the UIViewController goes away because the user dismissed it; that is to say, the download operation is associated with a particular instance of UIViewController.

Since structs have value semantics, it is almost conceptually incoherent to have an AnyCancellable associated with an "instance" of a struct. Structs don't have instances, they just have values. When you pass a struct as an argument to a function, it creates a copy. That means if the function called loadItems then only the function's own copy of the struct value would store the AnyCancellable, and the operation would be immediately cancelled when the function returns because your original copy of the value is not storing the AnyCancellable.

Upvotes: 10

Cy-4AH
Cy-4AH

Reputation: 4585

You don't need deinit and don't need to call

cancellables.forEach {
    $0.cancel()
}

I agree it's quite confusing that AnyCancellable have method cancel, that actually you don't need to call. Publishers are automatically cancelled, when cancellables got disposed. That is why you receive nothing if forget to store them somewhere.

Upvotes: 0

Related Questions