2

I've got an error "Closure cannot implicitly capture self parameter". Tell me please how it fix?

struct RepoJson {
...
    static func get(url: String, completion: @escaping (RepoJson!) -> ()) {
        ...
    }
} 

struct UsersJson {
    var repo: RepoJson!
    init() throws {   
        RepoJson.get(url: rep["url"] as! String) { (results:RepoJson?) in
            self.repo = results //error here
        }
   }
}
Yurii Petrov
  • 341
  • 2
  • 14
  • 3
    Possible duplicate of [Closure cannot implicitly capture a mutating self paramter](https://stackoverflow.com/questions/41940994/closure-cannot-implicitly-capture-a-mutating-self-paramter) – Moriya Jun 24 '17 at 09:55

1 Answers1

12

It's because you're using struct. Since structs are value, they are copied (with COW-CopyOnWrite) inside the closure for your usage. It's obvious now that copied properties are copied by "let" hence you can not change them. If you want to change local variables with callback you have to use class. And beware to capture self weakly ([weak self] in) to avoid retain-cycles.

farzadshbfn
  • 2,710
  • 1
  • 18
  • 38
  • I use a class, it's work well but I've got a new problem. RepoJson.get(url: rep["url"] as! String) { (results:RepoJson?) in if let repoData = results { self.repo = repoData } } self.repo did get value, here well. But when I print array repo equals nil. – Yurii Petrov Jun 24 '17 at 10:11
  • Do you print it right after you initialize the class? Because repo is going to be filled after callback returns value. – farzadshbfn Jun 24 '17 at 11:05
  • I create button and press it's and always equals nil. – Yurii Petrov Jun 24 '17 at 11:07
  • p.s: The way you're setting `self.repo = repoData`, it causes memory-leak because you captured self strongly. If you are 100% sure that this class is available when your callback returns, use it like this `{ [unowned self] repoData in self.repo = repoData }` but it seems to me that your use-case can not guarantee that `UsersJson` is available when callback returns hence use this: `{ [unowned self] repoData in self?.repo = repoData }` – farzadshbfn Jun 24 '17 at 11:10
  • Usually this scenarios need time, because there is a network call and it's gonna take some time. print it with a delay like (1 or 2 seconds) data will be available. – farzadshbfn Jun 24 '17 at 11:10
  • But your approach for calling network call has this problem that you don't know when the data is ready. I suggest use this approach: `struct UsersJson { var repo: RepoJson }`. call RepoJson.get individually and in it's callback: `let usersJson = UsersJson(repo: repoData)` – farzadshbfn Jun 24 '17 at 11:11
  • Because it retains the object and it's gonna cause memory-leak. This is how i do it, I usually take my Models as `struct` and they have a initializer with JSON (I'm using SwftiyJSON), if you're using system Json you must use JSONDecoder. and inside initializer I extract all my information from json data, and assign them to my custom variables. But how to fetch JSON? well I have another class as `Service` which runs and returns an JSON for specific request in a callback. I call service, fetch the json, intialize a Model and return This model in a callback for anyone listening. – farzadshbfn Jun 24 '17 at 13:14
  • I did understand you. Thanks. – Yurii Petrov Jun 24 '17 at 18:24