NSArrayController without loading a large dataset into an array - cocoa

I would like to use an NSArrayController to provide data to an NSTableView. The problem I am facing is that I do not want to pre-load all my data into an array and then use the array controllers setContent: method. My data model is a large existing code base that manages millions of records. It contains methods to efficiently return a set of data rows.
Following an example I found on limiting the number of objects in an NSArrayController, I tried subclassing NSArrayController and overriding the arrangedObjects: method to return an array proxy class I wrote. The array proxy class provided count: and objectAtIndex: methods. The object returned by objectAtIndex: is an NSDictionary. When I tried returning my array proxy from the arrangedObjects: method both count: and objectAtIndex: get called, but I also get an unrecognized selector error on my array proxy class for _valueForKeyPath:ofObjectAtIndex:. This looked like a private method, so I did not continue down this path.
I also thought of returning a smaller array of data from arrangedObjects:, but could not figure out how I would determine which rows the NSTableView was trying to display.
Is a datasource the "correct" way to interface with my existing data model or is there some way to make an NSArrayController work?

NSArrayController already works, with proxies and indexes and lazy-loading and the whole shabang. Have you tried just using it as-is? If afterwards you feel the need to micro-manage the data-loading, use NSFetchRequest. Subclass NSArrayController and add an initializer along these lines:
+ (id)arrayControllerWithEntityName: (NSString *)entityName error:(NSError **)error
{
id newInstance = [[[self alloc] initWithContent:nil] autorelease];
[newInstance setManagedObjectContext:[[NSApp delegate] managedObjectContext]];
[newInstance setEntityName:entityName];
[newInstance setAutomaticallyPreparesContent:YES];
[newInstance setSelectsInsertedObjects:YES];
[newInstance setAvoidsEmptySelection:YES];
[newInstance setAlwaysUsesMultipleValuesMarker:YES];
NSFetchRequest *dontGobbleRequest = [[[NSFetchRequest alloc] init] autorelease];
//configure the request with fetchLimit and fetchOffset an' all that
NSError *err;
if ([newInstance fetchWithRequest:dontGobbleRequest merge:YES error:&err] == NO) {
//better check docs whether merge:YES is what you want
if(*error && err) {
*error = err;
}
return nil;
}
return newInstance;
}
You'll have to do some research into the various possibilities and configurations, but you get the picture.

Related

Xcode Core-Data to-many relationship: addObject method error

I have the following core-data structure:
I am trying to add Vocabulary objects to the Group class.
My attempts at doing this with the [Group addObject: VocabularyObject] method have
come to no avail.
AppDelegate *delegate = [[AppDelegate alloc]init];
Group *group = [_arr objectAtIndex:indexPath.row]; //I have an array with 'Group' objects
//create vocabulary item
Vocabulary *vocabularyEntity = [NSEntityDescription insertNewObjectForEntityForName:#"Vocabulary" inManagedObjectContext:[delegate managedObjectContext]];
vocabularyEntity.prompt = #"Here is a cool prompt";
vocabularyEntity.definition = #"Here is an even cooler definition";
[delegate saveContext];
[group addTermsObject:vocabularyEntity];
I am getting this error, I used exception breakpoints and the error comes from the addTermsObject call.
[__NSDictionaryI addTermsObject:]: unrecognized selector sent to instance 0x74c4f70
The object I am trying to add is definitely a Vocabulary object, so i'm not exactly sure what the problem could be.
Any ideas?
Thank you!
The error message states that your
Group *group = [_arr objectAtIndex:indexPath.row];
is not a managed object as you expected, but an NSDictionary. Perhaps you fetched the array using
[fetchRequest setResultType:NSDictionaryResultType];
in the fetch request? In that case all the fetched objects are just dictionaries without
any connection to the managed object context, and you can't use these dictionaries to
establish any relationships.
UPDATE: Another error is here:
AppDelegate *delegate = [[AppDelegate alloc]init];
This allocates a new application delegate instead of using the existing one. This is
probably not what you want and you should replace it with
AppDelegate *delegate = [[UIApplication sharedApplication] delegate];

RestKit 0.20 — What is the preferred way to create a new NSManagedObject?

I'm curious to know what the best way is to create a new NSManagedObject in RestKit 0.20? Currently my code looks something like this:
#pragma mark - navigation buttons
- (void)createButtonDidTouch
{
// create new album object
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
NSManagedObjectContext *parentContext = RKObjectManager.sharedManager.managedObjectStore.mainQueueManagedObjectContext;
context.parentContext = parentContext;
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Album" inManagedObjectContext:parentContext];
Album *newAlbum = [[Album alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:context];
// pass object to create view to manipulate
AlbumCreateViewController *createViewController = [[AlbumCreateViewController alloc] initWithData:newAlbum];
createViewController.delegate = self;
createViewController.managedObjectContext = context;
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:createViewController];
navController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:navController animated:YES completion:nil];
}
#pragma mark - create view controller delegate
- (void)createViewControllerDidSave:(NSManagedObject *)data
{
// dismiss the create view controller and POST
// FIXME: add restkit code to save the object
NSLog(#"save the object...");
NSDictionary *userInfo = [KeychainUtility load:#"userInfo"];
NSString *path = [NSString stringWithFormat:#"/albums/add/%#/%#", userInfo[#"userID"], userInfo[#"apiKey"]];
[RKObjectManager.sharedManager postObject:data path:path parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
operation.targetObject = data;
} failure:^(RKObjectRequestOperation *operation, NSError *error) {
NSLog(#"create album error: %#", error);
}];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)createViewControllerDidCancel:(NSManagedObject *)data
{
// dismiss the create view controller
NSLog(#"delete the object...");
// FIXME: add restkit code to delete the object
[self dismissViewControllerAnimated:YES completion:nil];
}
I'm also curious to know what my responsibilities are for saving / deleting this object. If I POST to the server via RestKit is the managed object context saved?
What if I decide to cancel this creation process — what's the preferred way to delete this object?
Basically how much is RestKit doing for me, and what should I make sure I'm doing. I haven't found much documentation on this and would like to be clear on it.
When you initialize an RKManagedObjectRequestOperation for a given object, RestKit will obtain a permanent object ID for that object and then create a child managed object context whose parent context is the context the object is inserted into. The operation then executes the HTTP request to completion and obtains a response.
If the response is successful and the mapping of the response is successful (note that the mapping occurs within this private child context), then the private child context is saved. The type of save invoked is determined by the value of the savesToPersistentStore property (see http://restkit.org/api/0.20.0/Classes/RKManagedObjectRequestOperation.html#//api/name/savesToPersistentStore).
When YES, the context is saved recursively all the way back to the persistent store via the NSManagedObjectContext category method saveToPersistentStore (see http://restkit.org/api/0.20.0/Categories/NSManagedObjectContext+RKAdditions.html).
When NO, the context is saved via a vanilla [NSManagedObjectContext save:] message, which 'pushes' the changes back to the parent context. They will remain local to that context until you save them back. Keep in mind that managed object context parent/child hierarchies can be as long as you create within the application.
If the HTTP request failed or there was an error during the mapping process, the private context is not saved and the operation is considered failed. This means that no changes are saved back to the original MOC, leaving your object graph just as it was before the operation was started (except the object being sent, if temporary, now has a permanent object ID but is still unsaved).
The way you do it should works (calling each time the MOC in each of your VC), but is not "recommended".
What Apple suggests, just like any Core Data app, is the "pass the baton" style.
Nested contexts make it more important than ever that you adopt the
“pass the baton” approach of accessing a context (by passing a context
from one view controller to the next) rather than retrieving it
directly from the application delegate.
See here:
http://developer.apple.com/library/ios/#releasenotes/DataManagement/RN-CoreData/_index.html
As for your second question, RestKit should manage saving/updating your Core Data stack upon success of your api calls if everything is well mapped/setup.
From blake the RK creator:
if you POST or PUT a Core Data object, RK obtains a permanent object
ID for it and then creates a secondary managed object context, fires
the request, and maps the response (if possible). if the response and
the mapping are successful, it will either save it back to the parent
context or all the way back to the persistent store (i.e. into SQLite)
based on the value of the savesToPersistentStore.

NSKeyedArchiver: distinguishing between different instances of the same class

I'm implementing support for Lion's "Resume" feature in my OS X app.
I have a custom subclass of NSViewController in which I implemented the method
encodeRestorableStateWithCoder: as:
#implementation MyClass (Restoration)
-(void)encodeRestorableStateWithCoder:(NSCoder*)coder {
[coder encodeObject:_dataMember forKey:#"object_key"]; // I get the warning below when this line is executed for the second time
}
- (void)restoreStateWithCoder:(NSCoder *)coder {
_dataMember = [coder decodeObjectForKey:#"object_key"];
}
#end
However, since I have multiple instances of MyClass, different values are saved into the same key ("object_key") and I get the following warning from Cocoa:
NSKeyedArchiver warning: replacing existing value for key
'object_key'; probable duplication of encoding keys in class hierarchy
What is the best practice to overcome this problem?
Edit: I found here that each instance automatically has its own namespace to avoid collisions, so the problem might be in the way I'm manually calling encodeRestorableStateWithCoder to different instances with the same NSCoder object without telling it that these are different instances. However, I still can't figure out how to do that properly.
Thanks in advance!
To overcome this problem, it is possible to create a new NSMutableData where each of which is written by a separate (new) NSKeyArchiver, and store them all in an array which is stored in the original NSCoder object.
Here is an example for encoding the restorable state of subitems. The decoding part can be straight-forward given this code.
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[super encodeRestorableStateWithCoder:coder];
// Encode subitems states:
NSArray* subitems = self.items;
NSMutableArray* states = [NSMutableArray arrayWithCapacity: subitems.count];
for (SubItemClass* item in subitems)
{
NSMutableData* state = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:state];
[item encodeRestorableStateWithCoder:archiver];
[archiver finishEncoding];
[states addObject:state];
}
[coder encodeObject:states forKey:#"subitems"];
}

How to objects from a fetchedResultsController to a Plist?

Can someone help me. I have a coredata application but I need to save the objects from a fetchedResultsController into an NSDictionary to be used for sending UILocalNotifications.
Should I use an NSMutableSet, or a NSDictionary, or an array. I'm not used to using collections and I can't figure out the best way to do that.
Could you please give me clues on how to do that please ?
Thanks,
Mike
If I'm reading your question correctly, you're asking how you should pack objects into the userInfo dictionary of a UILocalNotification. Really, it's however works best for you; userInfo dictionaries are created by you and only consumed by you.
I'm not sure why you would be using an NSFetchedResultsController - that class is for managing the marshaling of managed objects between UI classes (like UITableView) efficiently, whereas here it sounds like you would be better off just getting an NSArray of results from your managedObjectContext and the corresponding request, like this:
NSError *error = nil;
NSArray *fetchedObjects = [myManagedObjectContext executeFetchRequest: myRequest error: &error];
if (array == nil)
{
// Deal with error...
}
where you have a pre-existing managed object context and request. You don't need to use an NSFetchedResultsController here.
From there, the simplest suggestion would be to build your userInfo dictionary like this:
NSDictionary* myUserInfo = [NSDictionary dictionaryWithObject: fetchedObjects forKey: #"AnythingYouWant"];
UILocalNotification *localNotif = [[UILocalNotification alloc] init];
// ... do other setup tasks ...
localNotif.userInfo = myUserInfo;
[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];
[localNotif release];
Then when it comes time to receive that notification, you can read that dictionary like this:
- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif
{
NSArray* myFetchedObjects = [notif.userInfo objectForKey: #"AnythingYouWant"];
for(id object in myFetchedObjects)
{
// ... do other stuff ...
}
}
Now, hopefully that's clarified how the userInfo dictionary works. I don't know the details of your app, so it's hard to say, but I'm suspicious that actually passing fetched objects is NOT what you want to do here, mainly because I'm not sure that you have any guarantee that the receiving delegate method will be working with the same object context as the sending method. I would suggest perhaps putting the entity name and predicate in the dictionary and then refetching the objects at receive time with whatever the current MOC is at that moment.
Good luck!

make two different fetch request per one method - looks like impossible

Dear community.
I was find a trouble for using a core data. Here is description:
From my AppDelegate i called my own class:
InitUpdateIXC *initAndUpdate = [[[InitUpdateIXC alloc] init] autorelease];
[initAndUpdate updateCarrierList:self.managedObjectContext];
Then i using there couple of methods, which update managedObjectContext, insert, add some entities e.t.c.
In this case i find limitation to using predicate twice per method:
First using working fine, and i seen results inside request:
NSFetchRequest *requestDestinationsForSale = [[NSFetchRequest alloc] init];
[requestDestinationsForSale setEntity:[NSEntityDescription entityForName:#"DestinationsListForSale"
inManagedObjectContext:managedObjectContext]];
[requestDestinationsForSale setPredicate:[NSPredicate predicateWithFormat:#"carrier.name like %#",carrierName]];
NSArray *destinationsForSale = [managedObjectContext executeFetchRequest:requestDestinationsForSale error:&error];
Inside the loop around MO:
for (NSManagedObject *destinationForSale in destinationsForSale)
{
for (NSManagedObject *code in [destinationForSale valueForKey:#"codesvsDestinationsList"])
{
i try to make new fetchRequest:
NSFetchRequest *requestDestinationWeBuy = [[[NSFetchRequest alloc] init] autorelease];
[requestDestinationWeBuy setEntity:[NSEntityDescription entityForName:#"DestinationsListWeBuy"
inManagedObjectContext:managedObjectContext]];
NSError *error = nil;
[requestDestinationWeBuy setPredicate:[NSPredicate predicateWithFormat:#"carrier.name like %#",carrierName]];
NSArray *destinationWeBuyList = [managedObjectContext executeFetchRequest:requestDestinationWeBuy error:&error];
ops... NSArray is empty...
if i do a same when i call method from AppDelegate:
[initAndUpdate updateRoutingTable:self.managedObjectContext];
It's a same class, same method, just called from main AppDelegate and little bit changed for using a just managed context, everything working fine.
Looks like managedObjectContext have final updates only when we leave class methods, which make updates.
Any comment will appreciated.
In your first fetch you are fetching on the entity DestinationsListForSale but in the second you are fetching on the entity DestinationsListWeBuy. The simplest explanation is that the same predicate does not produce the same result when applied to different entities.
Depending on the specifics of both the entities and the data being persistent at any one time, the same predicate will produce different outcomes when applied against different entities.
Indeed, that would be the expected behavior.

Resources