在闭包中引用属性需要明确的“自我”。使捕获语义明确

Posted

技术标签:

【中文标题】在闭包中引用属性需要明确的“自我”。使捕获语义明确【英文标题】:Reference to property in closure requires explicit 'self.' to make capture semantics explicit 【发布时间】:2015-09-19 07:03:42 【问题描述】:

尝试将 Web 服务中的 html 加载到 webview 中,我收到此错误:

在闭包中引用属性“webviewHTML”需要明确的“self”。使捕获语义明确

这是什么意思,如何将 HTML 字符串加载到我的 Web 视图中?

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();

【问题讨论】:

【参考方案1】:

self 的使用是对在闭包中引用(也称为捕获)构造(类/结构/枚举)的明确确认,这意味着 self 不会被解除分配,直到所述闭包被解除分配。

仔细想想,self 很可能被推断出来(事实上,当您在闭包外使用 webviewHTML 时),但这是一个有意的设计决策,而不是推断它,因为 Swift 是一种安全第一语言

【讨论】:

也许,但这会降低抽象级别并降低语言的规律性。此外,它更安全,因为使用了引用计数。【参考方案2】:

在回答这个问题之前,您必须知道什么是记忆周期。见Resolving Strong Reference Cycles Between Class Instances From Apple's documenation


现在您知道什么是内存循环了:

这个错误是 Swift 编译器告诉你的

“嘿,NSURLSession closure 正在尝试 保留 webviewHTML 堆,因此 self ==> 创建一个内存循环,我不 认为克拉克先生想要这里。想象一下,如果我们提出一个需要 永远,然后用户导航离开此页面。它不会 离开堆。我只是告诉你这个,但你克拉克先生必须创建一个对自我的弱引用并在闭包中使用它。”

我们使用[weak self] 创建(即capture)weak 引用。我强烈建议您查看附加链接,了解捕获的含义。

有关更多信息,请参阅斯坦福课程的this moment。

正确代码

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();


有关详细信息,请参阅此Shall we always use [unowned self] inside closure in Swift

【讨论】:

很好的答案,谢谢 - 现在这更有意义了。 也就是说你通常做的是:将data, response, error in更改为:[weak self] data, response, error in,然后在闭包中取消引用self而不是取消引用self?,因为self现在已生成成为nil-able(又名nullable)因此你必须打开它 这里有一些微妙之处。斯坦福课程的视频使用 [weak self] 来防止视图保留在堆上,这是为了避免删除引用循环。只有当闭包引用 self 并且 self 保持对闭包的引用时才会发生循环(这在 Apple 文档中明确说明:“如果您将闭包分配给类实例的属性,也会发生强引用循环,并且该闭包的主体捕获实例。”。这里的示例代码不保留对闭包的引用,因此没有强引用循环 @Dale 感谢您的评论。 这里的示例代码没有保留对闭包的引用,所以没有强引用循环你是什么意思?实例本身不是对post 函数有强引用吗?因此到task? @Honey 变量 task 和 request 都是函数 post() 中的局部变量。一旦 post 退出,这两个变量都会被释放,即使 NSURLSession 将继续异步执行。您可以通过在调用 post() 之后的代码和闭包中设置断点来确认此行为。在执行闭包之前,您应该看到 post exit 返回给调用者。

以上是关于在闭包中引用属性需要明确的“自我”。使捕获语义明确的主要内容,如果未能解决你的问题,请参考以下文章

闭包中的变量捕获详解

初始化变量:在所有成员初始化之前由闭包捕获的“自我”

SwiftUI 转义闭包捕获变异的“自我”参数

理解 Javascript/Node 中闭包的变量捕获

转义闭包捕获变异的“自我”参数,Firebase

为啥 Swift 闭包不捕获自我?