Swift 5 : What's 'Escaping closure captures mutating 'self' parameter' and how to fix it Swift 5 : What's 'Escaping closure captures mutating 'self' parameter' and how to fix it swift swift

Swift 5 : What's 'Escaping closure captures mutating 'self' parameter' and how to fix it


The problem is that ContentView is a struct, which means it's a value type. You can't pass that to a closure and mutate it. If you did, nothing would change, because the closure would have its own independent copy of the struct.

Your problem is that you've mixed your View and your Model. There can be many, many copies of a given View (every time it's passed to a function, a copy is made). You wouldn't want every one of those copies to initiate a request. Instead move this request logic into a Model object and just let the View observe it.


I ran into the same problem today and found this post, and then I followed @Rob Napier's suggestion, and finally made a working example.

Hope the following code can help (Note: it does not directly answer your question, but I think it will help as an example):

import Combineimport SwiftUIimport PlaygroundSupportlet url = URL(string: "https://source.unsplash.com/random")!// performs a network request to fetch a random image from Unsplash’s public APIfunc imagePub() -> AnyPublisher<Image?, Never> {    URLSession.shared        .dataTaskPublisher(for: url)        .map { data, _ in Image(uiImage: UIImage(data: data)!)}        .print("image")        .replaceError(with: nil)        .eraseToAnyPublisher()}class ViewModel: ObservableObject {        // model    @Published var image: Image?        // simulate user taps on a button    let taps = PassthroughSubject<Void, Never>()        var subscriptions = Set<AnyCancellable>()        init() {        taps            // ⭐️ map the tap to a new network request            .map { _ in imagePub() }            // ⭐️ accept only the latest tap            .switchToLatest()            .assign(to: \.image, on: self)            .store(in: &subscriptions)    }        func getImage() {        taps.send()    }    }struct ContentView: View {        // view model    @ObservedObject var viewModel = ViewModel()        var body: some View {        VStack {            viewModel.image?                .resizable().scaledToFit().frame(height: 400).border(Color.black)            Button(action: {                self.viewModel.getImage()            }, label: {                Text("Tap")                    .padding().foregroundColor(.white)                    .background(Color.pink).cornerRadius(12)            })        }.padding().background(Color.gray)    }}PlaygroundPage.current.setLiveView(ContentView())

Before tapping the button, the ContentView looks like this:

before tapping

After tapping the button (and wait for a few seconds), it looks like this:

after tapping

So I know it's working ^_^


Simply move the call from inside init to inside an onAppear somewhere in the body