[ NSManagedObjectContext deallocation issues - (Swift | Associated Objects) ]
I am hoping someone can explain why associated objects in the following example do not get automatically deallocated when the source/host object is deallocated. This example code below is somewhat contrived (apologies in advance), but it explains the my issue.
The example assumes a CoreData entity Product
with an string attribute sku
and the default CoreData stack provided by the Xcode template:
import UIKit
import CoreData
class ViewController: UIViewController {
@IBAction func createProduct(sender: AnyObject) {
let context = CoreDataHelpers.vendBackgroundWorkerContext()
let newProduct = CoreDataHelpers.newProduct(context: context)
newProduct.sku = "8-084220001"
do {
try newProduct.managedObjectContext?.save()
print("Product created [SKU: \(newProduct.sku ?? "NotDefined")]")
} catch {
print(error)
}
}
}
public class CoreDataHelpers {
public static let mainContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
public class func vendBackgroundWorkerContext() -> NSManagedObjectContext {
let managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.parentContext = self.mainContext
return managedObjectContext
}
class func newProduct(context context: NSManagedObjectContext) -> Product {
let newProduct = NSEntityDescription.insertNewObjectForEntityForName("Product", inManagedObjectContext: context) as! Product
return newProduct
}
}
when the createProduct
function is executed, a new PrivateQueueConcurrencyType Managed Object Context (MOC) will be vended and used by the new Product
Managed Object (MO). This above code works correctly - so far.
However! if I combine the first two lines of the createProduct
function such that:
let newProduct = CoreDataHelpers.newProduct(context: CoreDataHelpers.vendBackgroundWorkerContext())
then the app will crash at try newProduct.managedObjectContext?.save()
with a EXC_BAD_ACCESS
.
At first glance, this appears a little strange - as all we have done is refactored the code. Digging into the documentation, the managedObjectContext
property is declared as unowned(unsafe)
. This probably means that the created MOC has been deallocated and we have a dangling pointer (please correct me if my assumption is wrong).
In order to ensure that MOC does not get deallocated, I tried associating it with the MO itself. newProduct
:
class func newProduct(context context: NSManagedObjectContext) -> Product {
let newProduct = NSEntityDescription.insertNewObjectForEntityForName("Product", inManagedObjectContext: context) as! Product
var key: UInt8 = 0
objc_setAssociatedObject(newProduct, &key, context, .OBJC_ASSOCIATION_RETAIN)
return newProduct
}
This appears to works wonderfully - until I check in Instruments. It would appear that when the Product
MO is deallocated, the now associated MOC is not (shouldn't it be automatically deallocated when the source object is deallocated?)
My question is: Can someone explain where the additional reference is to the MOC that is preventing it from being deallocated? Have I created a retain cycle between the MO and the MOC?
Answer 1
You are probably creating a circular ownership (retain cycle).
Every managed object is owned by a managed context (the context owns the object) and setting the context as associated object means that the object now also owns the context.
Therefore, they won't get deallocated.
The real solution is to save the background context to a local property, the same you are doing with mainContext
.