Error within .sink() from Combine: Escaping closure captures mutating 'self' parameter

I'm trying to get my grips on the Combine framework from Apple and I'm unsure if I'm doing it right. I have the below error in my code. Can anybody please suggest how to get around this? Many thanks

Error: Escaping closure captures mutating 'self' parameter

import Combine 
import Foundation

// Model

protocol Fetchable {

    associatedtype T: Decodable

    var foo: [T] { get set }
}

extension Fetchable {

    internal mutating func fetch(
        from url: URL
    ) {

        let _: AnyCancellable? = URLSession.shared
        .dataTaskPublisher(for: url)
        .map({ $0.data })
        .decode(type: Self.T.self, decoder: JSONDecoder())
        .receive(on: DispatchQueue.main)
        .eraseToAnyPublisher()
        .sink(receiveCompletion: { completion in

            switch completion {

                case .finished:
                    print("Received completion: ", completion)
                    break

                case .failure(let error):
                    print("Received completion: ", completion, error)
            }
        }, receiveValue: { value in // Escaping closure captures mutating 'self' parameter

            print(".sink() received \(value)")
            self.foo.insert(value, at: 0) // Captured here
        })
    }
}

struct MyDecodableType: Codable {

    ...
}

// ViewModel

class MyClass: ObservableObject, Fetchable {

    @Published internal var foo: [MyDecodableType] = []
}

As you already identified, self is captured inside sink. Take in the account, that sink is not executed synchronously! It means, that when your function fetch will finished, you are not able to de-initialize instance of some class which implements Fetchable protocol. This is easy to solve, by using [weak self].

Be worry! The trouble just begin, even thought the code will compile and run. Now, when fetch finish (almost immediately after calling) nothing happens. Why? There is no AnyCancelable anymore, because it lives only while the fetch is executed ...

At least, you have to hold the reference of your AnyCancelable somewhere. There is no chance to declare stored property with the protocol. I suggest you to return it from fetch function, so you could hold (store) it while needed. This gives you another advantage, you will be able to cancel it.

2 Likes

I think Fetchable needs to be a reference type for async mutation to work. Try constraining your protocol (or your protocol extension) to AnyObject.

1 Like

Thank you both for your help, the code works with [weak self] and constraining Fetchable to class.