Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting a swift_unknownWeakRelease() in a possible block/closure retain cycle

I have a view controller that shows up at the start of my app to prepare the UIManagedDocument I'm using with Core Data. Problem: I keep getting a swift_unknownWeakRelease() crash when I run the app on an iPhone 4S device with iOS 7.1. Here's the code:

class SetupViewController: UIViewController {

    @IBOutlet weak var loadingView: UIActivityIndicatorView!

    override func viewDidAppear(animated: Bool)  {
        super.viewDidAppear(animated)
        let document = SPRManagedDocument.sharedDocument()

        document.prepareWithCompletionHandler {[unowned self] success in
            if success {
                self.dismissViewControllerAnimated(false, completion: nil)
            } else {
                self.loadingView.stopAnimating()
                UIAlertView(title: "Error", message: "Categories can't be loaded.", delegate: nil, cancelButtonTitle: "OK").show()
            }
        }
    }

}

I suspected that there's a strong reference cycle between the block and self since that's the only place I can see it potentially happening. True enough, if I change the capture list from [unowned self] to [weak self] or remove it altogether (keeping a strong reference to self inside the closure), the program continues just fine. This error doesn't happen when I run the app on an iPhone 5 simulator with iOS 8, or a 5S simulator with iOS 7.1.

How can I write my code differently so that the crash is avoided across all devices running iOS 7.0+? I'm convinced that unowned is the correct modifier. I expect self to still exist until the completionHandler is finished, so weak doesn't feel right. Here's the full log in the Debug Navigator if it helps:

Thread 1Queue : com.apple.main-thread (serial)
#0  0x0050c0ae in swift_unknownWeakRelease ()
#1  0x0012d760 in Spare.SetupViewController.(viewDidAppear (Spare.SetupViewController) -> (Swift.Bool) -> ()).(closure #1) at /Users/local.m.quiros/Development/spare-ios/Spare/View Controllers/SetupViewController.swift:28
#2  0x00108a8c in reabstraction thunk helper from @callee_owned (@unowned Swift.Bool) -> (@unowned ()) to @callee_owned (@in Swift.Bool) -> (@out ()) at /Users/local.m.quiros/Development/spare-ios/Spare/View Controllers/EditCategoryViewController.swift:180
#3  0x0012c68c in partial apply forwarder for reabstraction thunk helper from @callee_owned (@unowned Swift.Bool) -> (@unowned ()) to @callee_owned (@in Swift.Bool) -> (@out ()) ()
#4  0x00108ac4 in reabstraction thunk helper from @callee_owned (@in Swift.Bool) -> (@out ()) to @callee_owned (@unowned Swift.Bool) -> (@unowned ()) at /Users/local.m.quiros/Development/spare-ios/Spare/View Controllers/EditCategoryViewController.swift:180
#5  0x00108b18 in reabstraction thunk helper from @callee_owned (@unowned Swift.Bool) -> (@unowned ()) to @callee_unowned @objc_block (@unowned ObjectiveC.ObjCBool) -> (@unowned ()) at /Users/local.m.quiros/Development/spare-ios/Spare/View Controllers/EditCategoryViewController.swift:180
#6  0x00149e68 in __51-[SPRManagedDocument prepareWithCompletionHandler:]_block_invoke at /Users/local.m.quiros/Development/spare-ios/Spare/Objects/SPRManagedDocument.m:49
#7  0x3b4baf86 in _dispatch_barrier_sync_f_slow_invoke ()
#8  0x3b4b381e in _dispatch_client_callout ()
#9  0x3b4ba49e in _dispatch_main_queue_callback_4CF$VARIANT$mp ()
#10 0x3071b8a0 in __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ ()
#11 0x3071a174 in __CFRunLoopRun ()
#12 0x30684ebe in CFRunLoopRunSpecific ()
#13 0x30684ca2 in CFRunLoopRunInMode ()
#14 0x355de662 in GSEventRunModal ()
#15 0x32fd114c in UIApplicationMain ()
#16 0x00167060 in main at /Users/local.m.quiros/Development/spare-ios/Spare/main.m:16
like image 952
MLQ Avatar asked Feb 12 '23 22:02

MLQ


1 Answers

The problem here is with unowned reference. When self is released, the block still retains self and so tries to call self which is nil. Use weak in order to prevent this. Since weak is optional type inside block, you could use conditional unwrapping and execute other code as,

class SetupViewController: UIViewController {

  @IBOutlet weak var loadingView: UIActivityIndicatorView!

  override func viewDidAppear(animated: Bool)  {
    super.viewDidAppear(animated)
    let document = SPRManagedDocument.sharedDocument()

    document.prepareWithCompletionHandler {[weak self] success in
      if let weakSelf = self{
        if success {
          weakSelf.dismissViewControllerAnimated(false, completion: nil)
        } else {
          weakSelf.loadingView.stopAnimating()
          UIAlertView(title: "Error", message: "Categories can't be loaded.", delegate: nil, cancelButtonTitle: "OK").show()
        }
      }
    }
  }

}
like image 110
Sandeep Avatar answered Apr 08 '23 22:04

Sandeep