简体   繁体   中英

Why self is deallocated when calling [unowned self]

I'm trying write a simple closure as completion handler, and inside the closure set the text value of a textbox:

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

but on the line where I've defined [unowned self], I'm getting EXC_BREAKPOINT(code=EXC_I386_BPT,subcode=0x0) , and it's showing some assembly code as follow:

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)

I'm not sure what I've done wrong in here, based on the documentation. I've updated the question to contains the whole class. Also I've updated the question to update the text property of TextView on the main thread

Just to be clear about all the answers here--how you should fix this error depends on your needs.

The problem with the code in the original question is that self , which is a UIViewController , is being accessed from an asynchronously executed NSOperation block, and this is being done inside the completion handler of an asynchronous NSURLSessionTask .

By the time the runtime reaches self.textArea!.text , self has become nil . Under what circumstances will a UIViewController be deallocated? When you pop it out of the navigation stack, when it is dismissed, etc. I'm guessing that the code for btnSendRequestTapped(:) above isn't complete and has a popViewController() somewhere.

The solutions are:

1: To use weak instead of unowned . The difference between the two is that weak means the captured object ( self ) may be nil and turns it into an optional, while unowned means that you are certain self will never be nil and you can access self as is. Using weak means unwrapping self before using it. If it's nil, do nothing in the code.

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

2: The other solution is to cancel the NSURLSessionTask and its completion handler (which is dispatched into an NSOperationQueue property of NSURLSession called delegateQueue ) if the UIViewController is being popped out of the navigation stack.

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

3: Keep using [unowned self] , but don't pop the view controller until the operation block is done. I personally prefer this approach.

In short, keep self from being deallocated until you're actually done with it.

I'm not certain why, but I think it's working using weak instead of unowned . This could be a bug.

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

Your self is getting deallocated before your completion block runs. Don't forget that unowned is just the same as unsafe_unretained and will not be zeroed-out. You can try [weak self] instead but you will have to access it like this:

self?.txtArea!.text = "123"

You best using weak self in that case, just in case you pop out the view from the navigation stack.

Update: 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.
            }
    })

Swift 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.
                }
            })

I've found that this happens to me when I'm nil'ing out the last strong reference to a variable while inside of a notification closure, causing that object to be deinit'd on the spot, but then the same notification continues to be propagated to other objects-- and one of the objects which is observing that same notification was the deinit'd object- which was referencing self as [unowned self]. Apparently although the object is deinit'd it still tries to execute the block of code associated with the notification and crashes. I've also seen this happen when the last strong reference is nil'd out and a notification is about to be generated that will propagate to that object. It still crashes even though the deinit did a NSNotificationCenter.defaultCenter().removeObserver(self).

In those cases, one solution I've used with some success is to wrap a dispatch_async around the nil like so:

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

This causes the variable that's getting nil'd out to be destroyed after the notification fully propagates. Although it's worked for me once, it seems to still be touchy.

The bottom line: The only solution that seems to have been full-proof for me has been to eliminate the use of the closure, replacing the notification observer with the older object/selector type observer.

I think it's very possible it's a bug with the way [unowned self] works, and that Apple is just going to need to fix it. I've seen other people talk about problems they've had with [unowned self], needing to use [weak self] instead as a workaround. I think it's the same problem.

I ran into the same problem, running Xcode 6.4. Changing owned to weak seems to have solved the problem.

Two things:

  1. _swift_abortRetainUnowned does not happen all the time.
  2. _swift_abortRetainUnowned happens when the closure is assigned, not when it is executed, so in my case I am very sure self is not deallocated.

I found if the "unowned object" is a object of "Objective-C Class", for example a UIViewController, the program will be crashing. While if the "unowned object" is a object of "Swift Class", it will be OK.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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