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
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()
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With