12

Trying to load HTML from a web service into a webview, I get this error:

Reference to property 'webviewHTML' in closure requires explicit 'self.' to make capture semantics explicit

What does it mean, and how can I load the HTML string into my web view?

func post(url: String, params: String) {

    let url = NSURL(string: url)
    let params = String(params);
    let request = NSMutableURLRequest(URL: url!);
    request.HTTPMethod = "POST"
    request.HTTPBody = params.dataUsingEncoding(NSUTF8StringEncoding)

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
        data, response, error in

        if error != nil {
            print("error=\(error)")
            return
        }

        var responseString : NSString!;
        responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
        webviewHTML.loadHTMLString(String(responseString), baseURL: nil)
    }
    task.resume();
}
ThisClark
  • 14,352
  • 10
  • 69
  • 100

2 Answers2

13

Before answering this question you must know what a memory cycle is. See Resolving Strong Reference Cycles Between Class Instances From Apple's documenation


Now that you know what a Memory cycle is:

That error is Swift compiler telling you

"Hey the NSURLSession closure is trying to keep webviewHTML in the heap and therefor self ==> creating a memory cycle and I don't think Mr.Clark wants that here. Imagine if we make a request which takes forever and then the user navigates away from this page. It won't leave the heap.I'm only telling you this, yet you Mr.Clark must create a weak reference to the self and use that in the closure."

We create (ie capture) the weak reference using [weak self]. I highly recommend you see the attached link on what capturing means.

For more information see this moment of Stanford course.

Correct Code

func post(url: String, params: String) {

    let url = NSURL(string: url)
    let params = String(params);
    let request = NSMutableURLRequest(URL: url!);
    request.HTTPMethod = "POST"
    request.HTTPBody = params.dataUsingEncoding(NSUTF8StringEncoding)

    let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
        [weak weakSelf self] data, response, error in

        if error != nil {
            print("error=\(error)")
            return
        }

        var responseString : NSString!;
        responseString = NSString(data: data!, encoding: NSUTF8StringEncoding) 

        weakSelf?.webviewHTML.loadHTMLString(String(responseString), baseURL: nil)
        // USED `weakSelf?` INSTEAD OF `self` 
    }
    task.resume();
}

For in depth details, see this Shall we always use [unowned self] inside closure in Swift

mfaani
  • 33,269
  • 19
  • 164
  • 293
  • 1
    Great answer and thank you - this makes more sense now. – ThisClark Nov 10 '16 at 15:23
  • That being said what you normally do is: change `data, response, error in` to: `[weak self] data, response, error in` and then instead of dereferencing `self` in the closure you dereference it `self?` because `self` is now made to be `nil`-able (aka `nullable`) hence you have to unwrap it – mfaani Jul 23 '18 at 15:15
  • 2
    There are some subtleties here. The video of the Stanford course is using [weak self] to prevent the view being retained on the heap as a nicety NOT to remove a reference cycle. Cycles will only occur if the closure references self AND self keeps a reference to the closure (this is stated clearly in the Apple documentation: "A strong reference cycle can also occur if you **assign a closure to a property of a class instance**, and the body of that closure captures the instance. ". The sample code here doesn't retain a reference to the closure so there is no strong reference cycle – Dale Jan 07 '19 at 02:28
  • @Dale Thank you for your comment. _The sample code here doesn't retain a reference to the closure so there is no strong reference cycle_ what do you mean by that? Doesn't the instance itself have a strong reference to the `post` function? Hence to the `task`? – mfaani Jan 07 '19 at 20:03
  • @Honey Both variables task and request are local variables in the function post(). Once post exits both these variables are released even though the NSURLSession will continue to execute asynchronously. You can confirm this behaviour by setting breakpoints in the code AFTER calling post() and in the closure. You should see post exit back to the caller before the closure is executed. – Dale Jan 08 '19 at 02:16
  • @Dale So in what situation would the setup create a strong reference? Can you suggest an edit (to my answer) and share exact code of what you mean? – mfaani Jan 08 '19 at 11:34
  • @Honey To create a strong reference in the class to the closure just assign the closure to a variable of the class instance itself. – Dale Jan 10 '19 at 22:24
12

Usage of self is an explicit acknowledgement of referencing (also known as capturing) a construct (class/struct/enum) in a closure, the implication being that self will not be deallocated until said closure is deallocated.

When you think about it, self could have very well been inferred, (as it is, when you use webviewHTML outside a closure), but it is an intentional design decision not to infer it, as Swift is a safety first language.

Vatsal Manot
  • 17,695
  • 9
  • 44
  • 80
  • Perhaps, but this lowers the level of abstraction and reduces regularity in the language. Furthermore, it is only safer because reference counting is used. – Aluan Haddad Aug 15 '17 at 10:37