Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I return a pre-existing Core Data object at NSCoding initialization in Swift?

When an instance of my class is initialized using NSCoding, I want to replace it with an existing object in the Core Data database instead of calling:

super.init(entity: ..., insertIntoManagedObjectContext: ...)

as that would insert a new object into the database.

class MyClass: NSManagedObject, NSCoding {
    required init(coder aDecoder: NSCoder) {
        // Find a preexisting object in the Core Data database

        var ctx = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
        var fs = NSFetchRequest(entityName: "MyClass")

        // ... Configure the fetch request based on values in the decoder

        var err: NSErrorPointer = nil
        var results = ctx.executeFetchRequest(fs, error: err)

        // ... Error handling, etc

        newObject = results[0] as! MyClass

        // Attempt to replace self with the new object
        self = newObject // ERROR: "Cannot assign to 'self' in a method"
    }

    func encodeWithCoder(aCoder: NSCoder) {
        // Encode some identifying property for this object
    }
}

This was a fairly common Objective-C pattern, but I can't figure out how to replicate this in Swift 1.2, since assigning another object to self yields a compile error:

Cannot assign to 'self' in a method

and even if it did succeed, it seems Swift requires that I call NSManagedObject's designated initializer which would insert a new object into the database.


How do I replace an object with a pre-existing one in the database at initialization time? And if its not possible (please provide a reference if this is the case), what pattern should I be using instead?
like image 930
Andrew Avatar asked Jul 12 '15 21:07

Andrew


1 Answers

You can use awakeAfterUsingCoder(_:) for replacing self:

You can use this method to eliminate redundant objects created by the coder. For example, if after decoding an object you discover that an equivalent object already exists, you can return the existing object. If a replacement is returned, your overriding method is responsible for releasing the receiver.

class Item: NSManagedObject, NSCoding {

    @NSManaged var uuid: String
    @NSManaged var name: String?

    override init(entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?) {
        super.init(entity: entity, insertIntoManagedObjectContext: context)
    }

    required init(coder aDecoder: NSCoder) {
        let ctx = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
        let entity = NSEntityDescription.entityForName("Item", inManagedObjectContext: ctx)!

        // Note: pass `nil` to `insertIntoManagedObjectContext`
        super.init(entity: entity, insertIntoManagedObjectContext: nil)
        self.uuid = aDecoder.decodeObjectForKey("uuid") as! String
    }

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(self.uuid, forKey: "uuid")
    }

    override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
        let ctx = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext!
        let fetch = NSFetchRequest(entityName: "Item")
        fetch.predicate = NSPredicate(format: "uuid == %@", self.uuid)

        if let obj =  ctx.executeFetchRequest(fetch, error: nil)?.first as? Item {
            // OK, the object is still in the storage.
            return obj
        }
        else {
            // The object has already been deleted. so insert and use `self`.
            ctx.insertObject(self)
            return self
        }
    }
}
like image 119
rintaro Avatar answered Nov 15 '22 18:11

rintaro