Adding multiple entries to a set while preserving the object-link - objective-c

I have a question regarding the addition of multiple CoreData entries in multiple tables.
I have the following code:
while((item = [enumerator nextObject]))
{
DOCategory *cat;
cat = [[self categoryController] getNewCategory];
[cat setName:[item objectForKey: #"name"]];
[cat setDesc:[item objectForKey: #"description"]];
[cat setLastUpdate:updateTime];
//[[self categoryController] commitChanges];
}
I used to call the commitChanges after each enumeration, and that works, but takes a long time, especially on older devices as the initial load is quite substantial. Therefore I just want to save at the end of the whole operation.
What is the best way to add all those objectors to a NSSet (or NSArray) while preserving the link to the ManagedContext. Normally I would 'copy' them into the set, but that doesn't work here. A simple question, just have a block to seeing the best solution.
(I assume that I don't have to 'commit/save' after every new object I created, so the results are not written to the database yet, but are available for searches as there are different relational objects in the procedure)
Update:
After the suggestion below and more testing, it appears to me that when I haven't saved/committed the context it is not included NSFetchResultController. Is this right and supposed to be the way? Is there a way to do all the operations (including searches to create relations) in 'cache' and then commit once all is done?
Update 2:
In order to get the Managed Object I have a procedure in the Controller-class:
- (DOCategory *) getNewCategory{
return (DOCategory *)[NSEntityDescription insertNewObjectForEntityForName:#"Category" inManagedObjectContext:managedObjectContext];
}
It appears that the code runs fine, including cross references, until I come to adding the final object which uses all the other managed objects. There must be some problem in that code.

Your code is old school Objective-C 1.0 so it would be best to update it. You seldom use enumerators directly like that anymore.
The best way to handle this is to create the new managed objects by inserting them into the context using +[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:] this both creates the managed object and makes the context aware of it. The context will retain the managed object itself. That's were the "managed" of "managed object" comes from.
The normal practice when making a lot of changes is to change all the objects and then save the context only when you are done making all the changes.
Update:
It appears that the code runs fine,
including cross references, until I
come to adding the final object which
uses all the other managed objects.
There must be some problem in that
code.
That sounds like you might have a problem with the relationship being set properly in the data model itself. I can't say for certain without knowing what your data model looks like.
I discourage the use of cast with insertNewObjectForEntityForName: e.g.
(DOCategory *)[NSEntityDescription insertNewObjectForEntityForName:#"Category" inManagedObjectContext:managedObjectContext];
... because if you have a data model problem, such as not providing the name of the DOCategory class for the DOCatergory entity, the cast will obscure the problem. If there is no class defined the method will return an instance of the generic NSManagedObject class or if you accidentally define the wrong class for the entity e.g. MadeUpClassName instead of DOCategory then the cast will force the runtime to treat the returned object as a DOCategory object. That might work fine until way down in the code.
...it appears to me that when I haven't
saved/committed the context it is not
included NSFetchResultController. Is
this right and supposed to be the way?
Is there a way to do all the
operations (including searches to
create relations) in 'cache' and then
commit once all is done?
Not exactly sure what problem you are seeing. It is best practice to make all changes to all objects and then to save the context instead of saving after every change to any single object. Disk operations are expensive.
If your changes to the context are not reflected in the NSFetchResultsController (FRC) then you most likely did not implement the FRC delegate methods that update the tableview (see FRC docs.) Another possible error is that you've created two context accidentally. If you are using threads, each with a separate context (required practice) then you have to merge the background context with the front context before the changes made in the background are made known to the foreground.

Related

Do category names get serialized with encodeWithCoder style marshalling?

I have an app that is storing some data using the Cocoa/ObjC initWithCoder / encodeWithCoder style marshaling. Over the life of the app one of the classes that has been removed is an NSDate+Utils category.
The question is - if an NSDate+Utils is serialized to disk, and then deserialized with a later codebase when NSDate+Utils no longer exists; will it come back as an NSDate, or will it crash??
It won't crash. You just need to check whether it's nil. If that' nil, then just skip it, otherwise populate the data to your property. By the way, there is nothing to do with the codebase. It's just whether that local data being stored in the device. Codes are just things that tell device what or how to do stuff.
I wrote a small program to test this. Categories seem to get written as the base class - I assume as long as they don't add fields and/or mess with the withCoder methods.

CoreData - Refreshing an object doesn't change registeredObjects count

I'd like to free some memory releasing unused ManagedObjects from the context.
I prefer not to use reset and I want to release single objects using this function:
[self.managedObjectContext refreshObject:alarm mergeChanges:NO];
Now, if I log the registeredObjects count after this call it seems that nothing has changed. I still get the same number of objects that I have before the call.
Just to help you understand the example here it's a simple description of the model:
It is a typical Task list with Reminders. So there are Task objects and Alarm objects related to those tasks. Any task can have more than one alarm.
And here is the code that generates this problem:
Alarm *alarm = [NSEntityDescription insertNewObjectForEntityForName:ENTITY_ALARM inManagedObjectContext:self.managedObjectContext];
self.task.alarm = [NSSet setWithObject:alarm];
[self saveContext];
[self.managedObjectContext refreshObject:alarm mergeChanges:NO];
Since I'm in a detail view for the Task I'm not interested in maintaining in memory the Alarm object.
My questions are:
1) Why the count of registered objects doesn't change after calling refreshObject
2) Is this a good practice? Or should I go with reset, avoiding to release single objects?
Why the count of registered objects doesn't change after calling refreshObject
Because you aren't deleting it, you're just turning the object into a fault (so its details are removed from memory but not disk).
Is this a good practice? Or should I go with reset, avoiding to release single objects?
Reset destroys the in-memory representation of all of the objects in the context - you generally don't want to do that. Refreshing leaves the object as valid, just a fault. Refreshing means you need to drop all references to any objects in that context as they are invalid and can't be used again (you would need to refetch).
Generally, refreshing is a good approach, but only if you need to keep a reference to the object and aren't likely to need the object details very often. If you don't need the object then you should release it instead.

Make NSDocument "edited" when a binded control changes

I have an array of NSDictionaries and a NSDictionary iVar (*selectedDictionary) that points to one of the array's objects. *selectedDictionary points to a different object every time the user selects a different row in a NSTableView. Several GUI controls are binded to the selectedDictionary instance (IB).
I just want to make the NSDocument dirty (edited) every time the user alters the above controls. I think using Key Value Observing for ALL the objects in the array and all their kaypaths, is a bit insufficient. Any suggestions?
Thanks
NSDocument's support for marking a document as dirty comes directly from the NSUndoManager. The easiest way to change the document to dirty is to do an implementation of Undo, and this is basically going to mean doing the undo inside of the model class that the document is using (or the subclass of NSDocument if you choose to handle all storage directly in there).
Apple has documentation on this here:
http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/UndoArchitecture/Articles/AppKitUndo.html
Since you indicate you have an array of dictionaries, that's going to make it a bit more work to implement, but once you have nailed that, you'll be in good shape.
Alternatively, if you don't want to go with the freebie support provided by NSDocument and NSUndoManager, you can manually handle undo and use the updateChangeCount: method to modify the internal understanding of whether changes have occurred. This takes some work, and likely is a lot less useful than just setting up undo correctly.
As for the efficiency of observing all the objects in the array, I wouldn't worry about it unless you have profiled it and found it to be inefficient. KVO is really pretty darned efficient and we regularly observe multiple values in every element of arrays without seeing performance problems. You have to observe the array itself in order to handle adds and removes (assuming your arrays have this).
As far as I can tell, though, you have a selectedDictionary which is used to determine the other controls that are shown. In this case, you can use KVO to observe the value of selectedDictionary and when it changes, you can remove the observers from the previous selectedDictionary and add them to the keys in the current selectedDictionary. This is basically what bindings is doing in order to handle the display and setting, anyway.
One other consideration that I've used in the past is referenced in this StackOverflow post:
NSMutableDictionary KVO. If you look at my answer here, I outline a trick for getting notifications when a new key is added or an existing key is deleted. It also has the benefit of giving you a notification when there's any change. It's not always a great solution, but it does save some effort on coding the list of keys to observe.
Beyond that, you'll have to add every key you're expecting to have an effect on the saved state of the document.

How does one effectively handle temporary objects in Core Data since the objectID changes between temporary objects and permanent objects?

What is the best way to handle temporary objects in Core Data? I've seen solutions where temporary contexts are created, where they are inserted into nil contexts, etc.
However, here's the issue I'm seeing in both of these solutions. I'm using Core Data for my object model and and in some of my views store a NSSet of Core Data objects. The problem I have is when the object is stored, the objectID changes which effectively invalidates anything stored in any NSSet since the isEqual and hash are now different. While I could invalidate the object stored in the NSSet, it often is not practical and certainly not always easy.
Here's the things I've considered:
1) override isEqual method and hash on NSManagedObject (obviously bad)
2) do not place any NSManagedObject in a NSSet (use a NSDictionary where the key is always fixed)
3) use an entirely different type to store in NSSet where I could correctly implement the isEqual and hash code methods
Does anyone have a better solution for this?
ManagedObjects in an NSSet -- that sounds like a Core Data relationship. Why not simply store your temporary managedObjects in a relationship, and have Core Data take care of the problems you're now running into. Then you can concentrate on when and how to delete the temporary objects, or break the relationship or whatever is needed.
However, here's the issue I'm seeing in both of these solutions. I'm using Core Data for my object model and and in some of my views store a NSSet of Core Data objects. The problem I have is when the object is stored, the objectID changes which effectively invalidates anything stored in any NSSet since the isEqual and hash are now different.
tjg184,
Your problem here is not the transition to permanent IDs but that your container class depends upon an immutable hash. Hence, change your container class to an array or dictionary and this problem goes away. (You give up uniquing with an array but that is easy to handle with a trip through a transient set to perform the uniquing.)
Andrew
A possible solution would be to convert the temporary IDs to permanent ones using [NSManagedObjectContext obtainPermanentIDsForObjects:error:].
But be aware that this may be expensive, especially if you have a lot of objects you need to process this way.
You could possibly subclass NSManagedObject and override the willSave and didSave methods to remove and then re-add you objects to your set.
I actually ended up using a different approach, that of using a NIL context and providing a base class to handle insertion into a context. It works really well and is the cleanest solution I have found. Code can be found here... Temporary Core Data

Understanding NSManagedObject

In an existing project I have tried to introduce Core Data long after the project was created, so its model is already in place.
I have created the xcdatamodel and added my only class to it.
That class should act as a global storage for objects in my application.
The class properly implement NSManagedObject and I have verified it gets created and saved in context, also retrieved with a fetch result.
The way of saving data in this class is by means of NSMutableArray. But this is just not working. Here's a fragment of this class:
#interface WZMPersistentStore : NSManagedObject<NSCoding> {
NSMutableArray *persistentStorage;
}
#property(nonatomic,retain) NSMutableArray *persistentStorage;
-(void)add:(id)element;
-(void)remove:(id)element;
-(id)objectAtIndex:(NSUInteger)index;
-(NSUInteger)num;
#end
In the implementation I also override the initWithEntity like this:
- (id)initWithEntity:(NSEntityDescription*)entity insertIntoManagedObjectContext:(NSManagedObjectContext*)context {
NSLog(#"init with entity");
[super initWithEntity:entity insertIntoManagedObjectContext:context];
return [self init];
}
The init method only initialize the mutable array, and I can see from the log that it gets properly called by the app delegate when creating entity.
The add method just send message insertObject to persistentStorage.
The questions that come from this:
Am I doing "conceptually" right ? I
mean, is it correct to have instance
variable in managed object and
initialize like I did ?
when ns logging the size of the
persistentStorage I always get 0
even when logging a moment after the
addObject message (edit: that's not
true, I have verified again and I
correctly got 1 added).
The object stored in managed object
class trough persistentStorage are
normal class with attribute. Is
there something I need to do with
them ? I suppose not because I am
not getting any error at runtime.
No, that is not the "right" approach. You can perform initialization of instance variables in awakeFromFetch. Apple guidelines for NSManagedObject subclasses include the following:
You are also discouraged from
overriding
initWithEntity:insertIntoManagedObjectContext:,
dealloc, or finalize. Changing values
in the
initWithEntity:insertIntoManagedObjectContext:
method will not be noticed by the
context and if you are not careful,
those changes may not be saved. Most
initialization customization should be
performed in one of the awake…
methods. If you do override
initWithEntity:insertIntoManagedObjectContext:,
you must make sure you adhere to the
requirements set out in the method
description [...] (NSManagedObject Class Reference)
To really help, I'd need a deeper understanding of what you're trying to accomplish. Regardless, I strongly suggest combing through Apple's Core Data Programming Guide and sample code before proceeding.
I finally manage to solve this issue. Even if I am a newbie in objective-c, I think that introducing core data after the project is done, is not a good idea. Even if many claim it's easy. Unfortunately, all the people saying so, are showing as proof some really simple tutorial of one entity with one string attribute to change.
Instead for my project I ended up writing much code in addition to the existing one, plus some subclassing (NSManagedObject for example) which break the original model. This added code has also to be written carefully. Derived problem can be as simple as an attribute not saved, or dangerous as deleting wrong entities.
Infact, my problem was due to a wrong configuration in decode and encode method in the classes involved in the process of serialization.
For my questions:
-Point one still remain unanswered, because I am not yet confident in objective-c
-Point two, as I said the related object had some problem with encode/code.
-Point three, I was wrong, there's a lot of code to write, depending how complex is the relevant class.