【问题标题】:Why self is deallocated when calling [unowned self]为什么调用 [unowned self] 时释放 self
【发布时间】:2014-08-02 09:34:30
【问题描述】:

我正在尝试编写一个简单的闭包作为完成处理程序,并在闭包内设置文本框的文本值:

class ViewController: UIViewController {

    @IBOutlet var textArea : UITextView

    let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()

    let session:NSURLSession?


    init(coder aDecoder: NSCoder!)  {
        super.init(coder: aDecoder)

        session = NSURLSession(configuration: sessionConfig)
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    @IBAction func btnSendRequestTapped(sender : AnyObject) {

        let url:NSURL  = NSURL(string: "https://www.google.com")

        let sessionTask:NSURLSessionTask =
        session!.dataTaskWithURL(url, completionHandler: {
            [unowned self]
            (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
            let st:String = NSString(data: data,encoding: NSUTF8StringEncoding)

            println("\(st)")

            NSOperationQueue.mainQueue().addOperationWithBlock({
                () -> Void in
                self.textArea!.text = st
                })
            })

        sessionTask.resume()
    }
}

但是在我定义了 [unowned self] 的那一行,我得到了EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0),它显示了一些汇编代码,如下所示:

libswift_stdlib_core.dylib`_swift_abortRetainUnowned:
0x1001bb980:  pushq  %rbp
0x1001bb981:  movq   %rsp, %rbp
0x1001bb984:  leaq   0x176a7(%rip), %rax       ; "attempted to retain deallocated object"
0x1001bb98b:  movq   %rax, 0x792ce(%rip)       ; gCRAnnotations + 8
0x1001bb992:  int3   
0x1001bb993:  nopw   %cs:(%rax,%rax)

根据文档,我不确定我在这里做错了什么。我已将问题更新为包含整个班级。 我还更新了问题以更新主线程上 TextView 的文本属性

【问题讨论】:

  • @LordZsolt:这正是[unowned self] 的用途。
  • 您的对象在完成处理程序被调用之前被释放。您需要在某处保留对它的引用。
  • 我最近讨论了一个类似的问题stackoverflow.com/questions/24137090/… 它似乎在框架中存在一些错误,因为控制器显然可以在仍然引用它们时被释放(例如,当它们仍在屏幕上时)。
  • 顺便说一句,这里不需要[unowned self],因为无论如何都不会有保留周期——控制器不会保留闭包。

标签: swift


【解决方案1】:

在这种情况下,您最好使用weak self,以防万一您从导航堆栈中弹出视图。

更新:Swift 3.0

let task:URLSessionTask = session.dataTask(with: url, completionHandler: {
        [weak self] (data:Data?, response: URLResponse?, error:Error?) in

        /// Cool tip instead of using *strongSelf*, use ` as:
            if let `self` = self {
               /// Run your code here, this will ensure if self was deallocated, it won't crash.
            }
    })

斯威夫特 2.2

let sessionTask:NSURLSessionTask =
        session!.dataTaskWithURL(url, completionHandler: {
            [weak self] (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
                /// Cool tip instead of using *strongSelf*, use ` as:
                if let `self` = self {
                   /// Run your code here, this will ensure if self was deallocated, it won't crash.
                }
            })

【讨论】:

    【解决方案2】:

    只是为了清楚这里的所有答案——你应该如何解决这个错误取决于你的需要。

    原始问题中代码的问题是self,这是一个UIViewController,正在从一个异步执行的NSOperation块访问,这是在内部完成的异步的完成处理程序NSURLSessionTask

    当运行时达到self.textArea!.text 时,self 已变为nil。在什么情况下会释放UIViewController?当您将它从导航堆栈中弹出时,当它被关闭时等等。我猜上面的btnSendRequestTapped(:) 的代码不完整并且在某处有一个popViewController()

    解决办法是:

    1:使用weak 代替unowned。两者的区别在于weak 表示捕获的对象(self)可能是nil 并将其转换为可选,而unowned 表示您确定self 永远不会是nil 和您可以按原样访问self。使用weak 意味着在使用之前解开self。如果为 nil,则在代码中什么也不做。

    {[weak self] in
        if let strongSelf = self {
            // ...
            strongSelf.textArea!.text = "123"
        }
    }
    

    2: 如果UIViewController正在从导航堆栈中弹出。

    func btnSendRequestTapped() {
        // pop the view controller here...
        sessionTask.cancel()
        sessionTask.delegateQueue.cancelAllOperations()
    }
    

    3:继续使用[unowned self],但在操作块完成之前不要弹出视图控制器。我个人更喜欢这种方法。

    简而言之,保持self 不会被释放,直到您真正完成它为止。

    【讨论】:

    • 在您的(完美)示例和解释中,变量weakSelf 应称为strongSelf
    • 很好的解释。我已将所有无主的自我替换为弱自我。
    【解决方案3】:

    我在运行 Xcode 6.4 时遇到了同样的问题。将拥有的更改为弱似乎已经解决了这个问题。

    两件事:

    1. _swift_abortRetainUnowned 不会一直发生。
    2. _swift_abortRetainUnowned 发生在分配闭包时,而不是在执行时,所以在我的情况下,我非常确定 self 没有被释放。

    【讨论】:

      【解决方案4】:

      我发现当我在通知闭包内取消对变量的最后一个强引用时会发生这种情况,导致该对象在现场被取消初始化,但随后相同通知继续传播到其他对象——观察同一通知的对象之一是 deinit'd 对象——它将 self 引用为 [unowned self]。显然,尽管对象已取消初始化,但它仍会尝试执行与通知相关联的代码块并崩溃。当最后一个强引用为零并且即将生成将传播到该对象的通知时,我也看到了这种情况。即使 deinit 执行了 NSNotificationCenter.defaultCenter().removeObserver(self),它仍然会崩溃。

      在这些情况下,我成功使用的一种解决方案是在 nil 周围包裹一个 dispatch_async,如下所示:

      dispatch_async(dispatch_get_main_queue(), { () -> Void in
         someVariable = nil
      })
      

      这会导致在通知完全传播后被销毁的变量会被销毁。虽然它曾经对我有用,但它似乎仍然很敏感。

      底线:似乎对我来说完全证明的唯一解决方案是取消使用闭包,用旧的对象/选择器类型的观察者替换通知观察者。

      我认为这很可能是 [unowned self] 工作方式的错误,Apple 只需要修复它。我见过其他人谈论他们在 [unowned self] 方面遇到的问题,需要使用 [weak self] 作为解决方法。我认为这是同样的问题。

      【讨论】:

        【解决方案5】:

        我发现如果“unowned object”是“Objective-C Class”的对象,例如UIViewController,程序就会崩溃。而如果“unowned object”是“Swift Class”的对象,就OK了。

        【讨论】:

          【解决方案6】:

          您的self 在您的完成块运行之前被释放。不要忘记unownedunsafe_unretained 相同,不会被清零。你可以试试[weak self],但你必须像这样访问它:

          self?.txtArea!.text = "123"
          

          【讨论】:

          • 你为什么建议weak self?它也不是拥有引用,它可能不会崩溃,但仍然无法正常工作。
          • 你能澄清一下它是如何“不起作用”的吗? self 显然已被释放,因此这里只有两个选项,1) 使用 [weak self] 防止崩溃,并在释放时让 self 归零;或 2) 在其他地方保持对 self 的强引用,直到关闭运行。调用self.txtArea 的方式表明selfUIViewController,您可能不应该让其存活时间超过视图层次结构所保持的时间
          • @JohnEstropia,这绝对是因为自我被释放了,我的问题是为什么?我在这里做错了什么,基于关于解决闭包的强引用循环的苹果 swift 文档>>定义一个捕获列表它应该可以工作,但是正如你所说的 self 被意外释放了,我会说!
          • 好的,我看到您更改了问题的标题。您的视图控制器被释放的唯一原因是它在您的 URL 请求完成之前被解除/弹出/等。
          • "不要忘记 unowned 与 unsafe_unretained 相同,不会被清零。" unownedunsafe_unretained 不同,它将被清零。这就是他们如何保证在对象被释放后访问它时崩溃。
          【解决方案7】:

          我不确定为什么,但我认为它正在使用 weak 而不是 unowned。这可能是一个错误。

          session.dataTaskWithURL(url, completionHandler: {
                  [weak self]
                  (data:NSData!,response:NSURLResponse!,error:NSError!) -> Void in
                  let st:String = NSString(data: data,encoding: NSUTF8StringEncoding)
          
                  self!.txtArea!.text = "123"
                  }
              )
          

          【讨论】:

          • 如果我将其更改为 weak 并在主线程上运行 self!txtArea!.text ,它就可以工作,但主要问题仍然存在,除非,正如你所建议的那样,这将是一个错误
          • 执行[weak self] 然后使用self! 应该与执行[unowned self] 然后使用self 相同。任何差异都是错误。
          猜你喜欢
          • 2021-11-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-02
          • 1970-01-01
          • 2021-06-05
          • 1970-01-01
          相关资源
          最近更新 更多