I'm a little confused about how strong references are created and when reference cycles occur. Here's a simple example:
class Model {
var foo: Data?
func makeRequest(url: URL) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// assume request completes successfully
self.foo = data!
}
task.resume()
}
}
class ViewController: UIViewController {
var model = Model()
let url = URL(string: "abc.com")! // assume URL is valid
override func viewDidLoad() {
super.viewDidLoad()
model.makeRequest(url: url)
}
}
This is my understanding of how the references work in the code above:
- The variable
model
holds a strong reference to an instance of the Model class - URLSession holds a strong reference to its data task, which holds a strong reference to the closure.
- The closure escapes the function, and because it needs to update self, it holds a strong reference to the Model instance
- However, the Model instance does not hold a strong reference to the data task and therefore there is no reference cycle.
Is this correct? And if so, I really don't understand step 4. Why doesn't the Model instance hold a strong reference to the data task, since the task is created in a function of the Model class?
Note: I've seen several related questions, but I still don't understand why the Model instance does not hold a strong reference back to the session, task, or closure.
There is not a cycle here (as you note). However,
URLSession.shared
, which never goes away, does hold a reference to the task, which holds a reference toModel
. This means thatModel
cannot deallocate until the task completes. (IfModel
had aurlSession
property, then there technically would be a loop, but it wouldn't change anything in practice. "Loops" are not magical. If something that lives forever holds a reference to something, it will keep that object alive forever.)This is generally a good thing. URLSession tasks automatically release their completion block when they finish, so
Model
is only kept alive until the task is done. As long asModel
doesn't assume thatViewController
still exists (which it shouldn't), there is nothing wrong here as far as reference cycles.The one thing that is kind of bad about this code is that
Model
doesn't hold onto the task so it can cancel it, or even detect that one is in progress (to avoid making duplicate requests in parallel). That's not a major problem for simple apps, but is something that is useful to improve in more complex apps.