35

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

Amir
  • 9,577
  • 11
  • 41
  • 58
  • 9
    @LordZsolt: That's exactly what the `[unowned self]` is for. – John Estropia Jun 12 '14 at 07:02
  • @LordZsolt refer to here https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/AutomaticReferenceCounting.html – Amir Jun 12 '14 at 07:03
  • 4
    Your object is deallocated before the completionHandler is invoked. You need to keep a reference to it somewhere. – zneak Jun 12 '14 at 07:09
  • @CjCoax, can you give us more context for what `self` is? – Dash Jun 12 '14 at 07:10
  • @Dash self is an instance of UIViewController and this code is inside ibaction of a button tap – Amir Jun 12 '14 at 07:11
  • @zneak, agree but why? according to apple documentation on Resolving Strong Reference Cycles for Closures it shouldn't (Please correct me if I'm wrong) – Amir Jun 12 '14 at 15:30
  • 1
    I recently discussed a similar problem http://stackoverflow.com/questions/24137090/custom-uitableviewcell-delegate-pattern-in-swift/24142368#24142368 It appears there some bug in the framework because the controller can apparently be deallocated while there are still references to them (e.g. when they are still on the screen). – Sulthan Jun 12 '14 at 15:48
  • 2
    BTW, you don't need `[unowned self]` here because there won't be a retain cycle anyway - the controller doesn't retain the closure. – Sulthan Jun 12 '14 at 15:49
  • @Sulthan, you are right, but I changed my closure to be a [@]lazy property of UIViewController class and passed it to dataTaskWithURL as completionHandler parameter, still getting same issue, as you mentioned I believe this is a bug in the framework – Amir Jun 12 '14 at 15:54
  • [The very same error](http://stackoverflow.com/q/36231069/3402095) in a different circumstances (I've made intentionally a retain cycle just for testing purposes ) ... There is an answer which partially explains the issue (I say partially, because there is no detailed explanation what is happening under the hood and why it is random etc) Still, it may give you an idea of what is happening. – Whirlwind Apr 09 '16 at 15:21

7 Answers7

46

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.

Matthew Quiros
  • 13,385
  • 12
  • 87
  • 132
9

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"
        }
    )
Dash
  • 17,188
  • 6
  • 48
  • 49
  • It is working if I change it to weak and also run self!txtArea!.text on the main thread, but still the main question remains, unless, as you've suggested, it would be a bug – Amir Jun 12 '14 at 15:06
  • 10
    Doing `[weak self]` and then using `self!` should be identical to doing `[unowned self]` and then using `self`. Any difference would be a bug. – user102008 Sep 20 '14 at 21:05
5

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.
                }
            })
Tal Zion
  • 6,308
  • 3
  • 50
  • 73
3

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"
John Estropia
  • 17,460
  • 4
  • 46
  • 50
  • Why would you suggest `weak self`? It's not an owning reference either, it may not crash but it's still not going to work. – zneak Jun 12 '14 at 07:08
  • 3
    Can you clarify how it's "not going to work"? The `self` was clearly deallocated so there's just two options here, 1) To prevent crashing by using `[weak self]` and let `self` zero out when it's released; or 2) Keep a strong reference to `self` somewhere else until the closure runs. The way `self.txtArea` was called suggests that `self` is a `UIViewController`, which you probably shouldn't keep alive longer than the view hierarchy keeps it – John Estropia Jun 12 '14 at 07:39
  • @JohnEstropia, It definitely is because the self is deallocated, by my question is why? What am I doing wrong in here, based on apple swift documentation on Resolving Strong Reference Cycles for Closures>>Defining a Capture List it should be working, but as you said self is deallocated unexpectedly,I would say! – Amir Jun 12 '14 at 15:36
  • OK, I see that you changed the title of your question. The only reason for your view controller to be deallocated is if it was dismissed/popped/etc before your URL request finishes. – John Estropia Jun 13 '14 at 01:05
  • "Don't forget that unowned is just the same as unsafe_unretained and will not be zeroed-out." `unowned` is NOT the same as `unsafe_unretained` and it WILL be zeroed-out. That's how they can guarantee to crash when you access it after the object is deallocated. – user102008 Aug 20 '14 at 00:35
  • `unowned` goes to an undefined state instead of being zero. Anyway it reliably crashes for access on this state at least in debug build. – eonil Apr 30 '15 at 05:58
2

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.

John Bushnell
  • 1,851
  • 22
  • 29
2

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.
JeanLuc
  • 4,783
  • 1
  • 33
  • 47
Yange Wang
  • 21
  • 1
1

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.

migrant
  • 498
  • 5
  • 14