Persisting Pointer to Object - ios

Problem: Need Unique Identifier
I'm saving a custom object using NSArchiver. It retains all of my objects data, however, everytime I archive and unarchive it gives them new addresses
"<Item: 0x17005d070>",
"<Item: 0x17005e4b0>",
"<Item: 0x17005e4e0>"
"<Item: 0x170059fe0>",
"<Item: 0x170059ec0>",
"<Item: 0x17005a0a0>"
For the same 3 objects.
This causes problems because I need to hold a copy of some of the items and persist this copy, and later compare the copy to the original for equality [currentItem isEqual:oldItem]; Even when this should return true (i.e. the object is the same in terms of name, location, etc.) it will return false because the pointers are different. What's the solution to this? I've thought about adding a uniqueID to each object and then just storing that uniqueID, but that seems like overkill for what I'm trying to do.
Solution:
You can't rely on an objects memory address as a unique identifier. Use NSUUID.

The problem is that you have not implemented isEqual: for your Item class. Implement it. Otherwise, as you've observed, we fall back on NSObject's definition of isEqual:, which is identicality (i.e. these are one and the same object). It is up to you to tell Cocoa that an Item should return true if the object is the same in terms of name, location, etc.; it doesn't magically know this.

Related

Core data: Object ID changes

I'm trying to fetch object by object ID in a simple core data implementation. Using this api
let targetObj = CoreDataManager.sharedInstance.privateQueueContext.object(with: self.objectID) as! MyObj
but it seems object id changes. Here is the console log
(lldb) po self.objectID
0xd000000000280000 <x-coredata://551DDB76-537D-41FA-B923-F772E5EE5D29/MyObj/p10>
(lldb) po CoreDataManager.sharedInstance.privateQueueContext.object(with: self.objectID).objectID
0xd000000000280006 <x-coredata://551DDB76-537D-41FA-B923-F772E5EE5D29/MyObj/p10>
Please let me know if I'm doing anything wrong. Actually I'm book keeping these objectIds in an queue and need to dequeue the object based on these objectIds.
Thanks
Ankit
The only difference you're seeing when you print each object ID is the address in memory where the object ID is stored.
So, if you take those two objects and compare them with == they will be different, because that is testing referential equality.
If you compare those two objects with isEqual:, it will return true, because isEqual: is testing whether the values they each represent are equal.
You can't reliably compare objects by reference, you need to use isEqual:.

About the benefit of objectWithID:

The doc says:
If the object is not registered in the context, it may be fetched or
returned as a fault. This method always returns an object. The data in
the persistent store represented by objectID is assumed to exist—if it
does not, the returned object throws an exception when you access any
property (that is, when the fault is fired). The benefit of this
behavior is that it allows you to create and use faults, then create
the underlying data later or in a separate context.
I'm thinking about the last sentence:
The benefit of this behavior is that it allows you to create and use faults, then create the underlying data later or in a separate context.
Does it mean I can use objectWithID: with an arbitrary ID to get a fault handle of an non-existing object first then later create the object with ID? But how can I assign an arbitrary ID to the new object?
In general, Yes you can get a handle to a non existing item an later create that item.
But, since you don't know what ID will be assigned to the item these is not very useful in that case.
You could use obtainPermanentIDsForObjects:error: to obtain the object final ID, but, this is a trip to the store, and will have a performance penalty.
You can use objectWithID: to "warm up" the coordinator cache. in this manner you may fetch objects in the background, and use this method in another context then access these items without hitting the store (much better performance).
Since every NSManagedObjectID must initially come from a fulfilled NSManagedObject and there is no way to create one from scratch, the only possible way to "create the underlying data later" is meaningless, as follows:
NSManagedObjectID *objID = object.objectID;
[moc deleteObject:object];
…
object = [moc objectWithID:objID]; // Deleted so non-existing
[moc insertObject:object]; // Kinda of resurrecting the deleted object, but not really since the data are gone only ID is left. So it is creating a new object with the old ID. But what's the point?
// Fill data into object
…
[moc save:NULL];
If you use -objectWithID:, it will return a fault if the object is not already registered in the managed object context (ie. only if the object hasn't already been fetched and hasn't been faulted in). In the case that it does return a fault, you do not need to do anything to "create the object". Simply accessing the attributes of the object will automatically fire the fault and let you access its data. There is no additional work needed on your part to create additional objects.

NSArray containsObject method inconsistent results

I have an NSMutableArray called selectedUsers to which I am adding objects using a method called addUser. The objects being added are most often of type PLManagedUser (a core data managed object) although sometimes the object could be a string. In any case, if the array already contains the object, I do not want to add it to the array. Here is the code:
- (void)addUser:(id)user withTitle:(NSString *)title {
if (![_selectedUsers containsObject:user]) {
[_selectedUsers addObject:user];
}
}
I have noticed that if I try to add the same user back to back using the above method, the containsObject catches it, and duplicates are not added. However, if I add the same user (with the same memory address) after having added other objects in between, the duplicate will be added.
I am printing the contents of the array each time I add something to confirm that the duplicate objects are in the array.
My question is, is there any obvious reason why containsObject isn't consistently working here?
You need to provide the ability for an object to identify itself as equal to another object of the same class, and to do this you implement the isEqual: and hash methods.
Having said that, the explanation in your question is the opposite of what I would have expected.

Sending NSNotifications to all objects of a class

I have an object that can be selected by a user click. With the current requirements of the app, at any time, there is no more than one of these items selected at any point during app execution.
I implemented a mechanism to enforce this, as follows:
Each of these objects has a unique identifier as a property.
When each object is created, it subscribes to the NSNotificationCenter listening for the MY_OBJECT_SELECTED notification.
When each object is selected, it posts the MY_OBJECT_SELECTED notification, with its unique Id as part of the userInfo dictionary.
Then, when each object receives the notification, it checks to see if its id is the same as the one in the userInfo. If it is, it does nothing, but if it isn't, it sets itself to unselected.
Is this a decent approach to the problem? If not, how would you do it?
It is a decent way of doing it, although it is not very efficient. The more objects you have, the more time you spend comparing IDs. The easiest way is to store your object pointers and IDs in a map table (or similar) and remember the last selected object. Whenever you select a new object, you clear the selection flag of the last selected object, then look up the new object and set its selection flag. It requires you to keep a collection of your objects, though.
The time required to update selections with this approach is independent of the number of objects you have.
If the object is spread all over the app,i.e. if it is a member in various classes. You can have a global object of same type and assign it to only that object which has been touched. In steps it will be like:
Have a global variable of object type.
At any object touch assign globalObject = currentObject;
do all operations on globalObject throughout app like calling methods and modifying object members(have a check for nil to ensure safety).
Reassign to different object with new touch.

Deleting Core Data objects from in-memory store turns them into faults but does not erase them

I have a Core Data stack based on the NSInMemoryStoreType store. And I've noticed that deleting objects doesn't really remove them or make them nil, bur rather simply turns them into faults.
For example, (MyManagedObjectEntityClass as well as the <> identifier are placeholders):
MyManagedObjectEntityClass *o = [NSEntityDescription insertNewObjectForEntityForName:#"<MyManagedObjectEntityClass Entity Name>" inManagedObjectContext:self.localContext];
NSLog(#"\n%#", o);
[self.localContext deleteObject:o];
NSLog(#"\n%#", o);
Will log that the object is still there only that it's data is a fault.
And adding [self.localContext save:nil]; after the delete doesn't change this either.
I was hoping I could at some point test the o variable for nil, in which case I'd reload the object - but it seems I can't.
Just in case, yes, I know I could instead test o for -isFault. But thing is, extrapolate this test to an NSSet and I can't just rely on [[set anyObject] isFault] to conclude that all objects in that set have been removed (Ideally the set's count would be 0, but all objects are still there as faults).
So I'm wondering if it's possible at all or what alternate approach could I take to be able to test that objects have been deleted in a way transparent to the fact that they are managed objects.
This is not actually a Core Data issue. C (and by extension Objective-C) doesn't work like that.
The deleteObject: method takes one argument, a pointer to an object. It can change the object (like setting its isDeleted flag), or it can do other things related to the object (like deleting it from its managed object context). It cannot change the value of the pointer itself. So whatever it does or should do, C says that once it's done, the pointer that you pass in still points to the same location in memory. As a result it's actually impossible for that method to force that pointer to be nil in this language. If you want it to be nil, you have to change that yourself. (As an aside, it would have been possible to implement the method to take a pointer to pointer argument, which could modify your pointer. This would have no effect on other references such as those in arrays, though, so it would be kind of pointless).
This is why the isDeleted method is public, so that if you have a pointer to this object in some other location, you can check whether it has been deleted before attempting to use it.
If that's not convenient enough (and it often isn't), Core Data also provides NSManagedObjectContextObjectsDidChangeNotification and NSManagedObjectContextDidSaveNotification. You can use these anywhere in your app to get notified of changes to the context and respond in whatever way is appropriate (updating an array, for example). These notifications both try to help you out by providing lists of inserted, updated, and deleted objects. Use those when possible to check whether you actually need to update your references.

Resources