2

I have the following Struct that I want to initialize, and then use its method query() to mutate its result property.

Query() sends and fetches JSON data, then decodes it to a String. When I declare query() as a mutating function, I receive the error "Escaping closure captures mutating 'self' parameter" in my URLSession.

What do I need to change?

The call:

var translation = Translate(string: "hello", base: "en", target: "de", result: "")
translation.query()
let translated = translation.result

The struct:

struct Translate {
    
    let string: String, base: String, target: String
    var result: String
    
    mutating func query() {
        
        let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
        let bodyData = try? JSONSerialization.data(withJSONObject: body)

        guard let url = URL(string: "https://libretranslate.com/translate") else { return }
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        
        request.httpBody = bodyData
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            
            guard let data = data, error == nil else {
                print(error?.localizedDescription ?? "No data")
                return
            }
            
            DispatchQueue.main.async {
                let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
                if let responseJSON = responseJSON as? [String: Any] {
                    if responseJSON["translatedText"] != nil {
                        self.result = responseJSON["translatedText"] as! String
                    }
                }
            }
            
            return
            
        }
        .resume()
        
    }
    
}

Xcode error: enter image description here

LuLuGaGa
  • 13,089
  • 6
  • 49
  • 57
  • 1
    https://stackoverflow.com/questions/41940994/closure-cannot-implicitly-capture-a-mutating-self-parameter#:~:text=Capturing%20an%20inout%20parameter%2C%20including,explicit%20(and%20thereby%20immutable). – Tushar Sharma Apr 03 '21 at 09:49
  • I read through this post, and it does help address the error, by first assigning the JSON response to a property in query() and then assigning that to the property in the Struct. However, this doesn't work asynchrounously, so the result is always nothing. –  Apr 03 '21 at 10:00

1 Answers1

1

There are many issues in the code.

The most significant issue is that the URLRequest is asynchronous. Even if no error occurred result will be always empty.

You have to add a completion handler – it fixes the errors you got by the way – and it's highly recommended to handle all errors.

Instead of JSONSerialization the code uses JSONDe/Encoder

struct Translation : Decodable { let translatedText : String }

struct Translate {
    
    let string: String, base: String, target: String
    
    func query(completion: @escaping (Result<String,Error>) -> Void) {
        
        let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
        do {
            let bodyData = try JSONEncoder().encode(body)
            
            let url = URL(string: "https://libretranslate.com/translate")!
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            
            request.httpBody = bodyData
            request.addValue("application/json", forHTTPHeaderField: "Content-Type")
            
            URLSession.shared.dataTask(with: request) { data, response, error in
                if let error = error { completion(.failure(error)); return }                                       
                completion( Result{ try JSONDecoder().decode(Translation.self, from: data!).translatedText} )
            }                
            .resume()
        } catch {
            completion(.failure(error))
        }
    }
}

let translation = Translate(string: "hello", base: "en", target: "de")
translation.query() { result in
    DispatchQueue.main.async {
        switch result {
            case .success(let translated): print(translated)
            case .failure(let error): print(error)
        }
    }
}

Both exclamation marks (!) are safe.

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
vadian
  • 274,689
  • 30
  • 353
  • 361
  • Be careful about calling the completion block on different threads in the error case and the success case. That can be a source of bugs. The caller might not be expecting that behaviour – Shadowrun Apr 03 '21 at 11:32
  • @Shadowrun You're absolutely right, thanks. I edited the answer. – vadian Apr 03 '21 at 13:24