繁体   English   中英

为什么在呼叫[无名的自我]时将自我释放

[英]Why self is deallocated when calling [unowned self]

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

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

但是在我定义了[ EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0) 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的text属性

只是为了弄清这里的所有答案-如何解决此错误取决于您的需要。

原始问题中的代码存在的问题是,从异步执行的NSOperation块访问了self (即UIViewController ,并且这是在异步 NSURLSessionTask的完成处理程序中完成的。

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

解决方案是:

1:使用weak而不是unowned 两者之间的区别在于, weak表示被捕获的对象( self )可能为nil并将其转换为可选对象, unowned意味着您确定self永远不会为nil并且可以按原样访问self 使用weak意味着在使用self之前先解开self 如果为零,则在代码中不执行任何操作。

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

2:另一种解决方案是取消NSURLSessionTask和其完成处理程序(其被分派到一个NSOperationQueue的属性NSURLSession称为delegateQueue如果) UIViewController被弹出导航堆栈的。

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

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

简而言之,在真正完成self之前,不要释放self

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

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"
        }
    )

在完成区块运行之前,您的self已被释放。 不要忘了, unowned只是一样unsafe_unretained并不会归零了。 您可以改用[weak self]但您必须像这样访问它:

self?.txtArea!.text = "123"

在这种情况下,您最好使用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.
                }
            })

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

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

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

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

最重要的是:对我来说,唯一可行的解​​决方案是消除闭包的使用,用较旧的对象/选择器类型观察器代替通知观察器。

我认为这很可能是[无人认领的自我]运作方式的错误,而Apple仅需要对其进行修复。 我见过其他人谈论他们在[无主的自我]时遇到的问题,需要使用[弱的自我]作为解决方法。 我认为这是同样的问题。

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

两件事情:

  1. _swift_abortRetainUnowned并非始终都发生。
  2. _swift_abortRetainUnowned发生在分配闭包时,而不是在执行闭包时发生,因此对于我来说,我确定self不会被释放。

我发现如果“未拥有的对象”是“ Objective-C类”的对象,例如UIViewController,则程序将崩溃。 如果“无主对象”是“ Swift类”的对象,则可以。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM