0

I'm working with Core Data for the first time, with the Stanford iOS app development course as a guide. I pretty much copied the code from the demo app (of course I adjusted it to my needs), but I'm having two problems currently.

My app is a map view which on the tap of a button presents a modal view controller. This modal view checks whether a UIManagedDocument was created. If not, it creates one and inserts the data. This data is coming from a property list (258 items, so nothing too excessive). If it was created already (by previously displaying that view), if my logic holds, it should be safe to assume it also has content because the NSManagedObjects are created at the same time a document is created. The first run works perfectly fine, the table loads and all my data is correctly displayed.

However, when I dismiss and then re-display my modal view, the table stays empty. I'm checking the document's state, which is UIDocumentStateNormal, so querying it should be fine. But it isn't: my fetchedResultsController returns 0 rows. If I understand UIManagedContext correctly, the behavior I'm experiencing could be caused by a wrong/different context, but I make sure that: 1) I pass my document (not just the context) to the modal view in prepareForSegue:sender, and 2) I pass my document with the context back to the presenting view when the modal view is being dismissed. That's why I think it's probably not the context, but something else.

One other thing: inserting the 258 records when the app is first launched is fast enough in the simulator. However, on my phone it could take a whole 13 seconds. The insertion code is shown below (modified for readability):

+ (Department *)departmentName:(NSString *)name
                withAttributes:(NSDictionary *)attributes
                     inContext:(NSManagedObjectContext *)context {

    Department *department = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:context];
        department.name = name;

        NSArray *informationElements = [attributes objectForKey:@"information"];

        for (int i = 0; i < [informationElements count]; i++) {
            NSString *informationValue = [[informationElements objectAtIndex:i] objectForKey:@"value"];

            if ([[[informationElements objectAtIndex:i] objectForKey:@"description"] isEqualToString:@"phone"]) {
                department.phone = informationValue;
            } else if ([[[informationElements objectAtIndex:i] objectForKey:@"description"] isEqualToString:@"email"]) {
                department.email = informationValue;
            } else if ([[[informationElements objectAtIndex:i] objectForKey:@"description"] isEqualToString:@"web"]) {
                department.website = informationValue;
            }
        }   
    return department;
}

To be clear: this code works just fine, but it's really slow. It's encapsulated in a method which is called exactly 258 times. informationElements has at most three elements, which means there are 258 * 3 = 774 loops maximum. In reality it's much less than that, but even if it were 774, that shouldn't take 13 seconds, right?

The snippet below shows the initialization of UIManagedDocument:

if (![[NSFileManager defaultManager] fileExistsAtPath:[self.database.fileURL path]]) {
    [self.database saveToURL:self.database.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
        [self setupFetchedResultsController];
        [self fetchDepartmentsIntoDocument:self.database];
    }];
} else if (self.database.documentState == UIDocumentStateClosed) {
    [self.database openWithCompletionHandler:^(BOOL success) {
        [self setupFetchedResultsController];
    }];
} else if (self.database.documentState == UIDocumentStateNormal) {
    [self setupFetchedResultsController];
}

fetchDepartmentsIntoDocument reads the property list and then runs a loop which calls departmentName:withAttributes:inContext for every property list item.

If anyone could provide me with some help, it will be much appreciated!

Scott Berrevoets
  • 16,921
  • 6
  • 59
  • 80
  • have you tried it on a different thread? – owen gerig Dec 23 '11 at 20:46
  • I have not. Is there a chance that there's just too much data to handle it fast enough? – Scott Berrevoets Dec 23 '11 at 20:48
  • could be, i use core data to save my singleton object and it works fine but yours does seem a lot more complicated. i say it as kind of a blanket statement for something that works but is slow. unless there was reason to suspect otherwise. – owen gerig Dec 23 '11 at 20:50
  • Ok, I've limited my loop to do only 15 items and that runs a lot faster, so I'll try running it in a thread. However, the issue of my data not being saved remains. – Scott Berrevoets Dec 23 '11 at 20:54

2 Answers2

1

For the speed issue, I would look into using predicates; that should speed things up a great deal!

Predicates make it so that the context returns only the values needed based on whatever criteria you choose. The reason they are faster is because it does not have to convert each stored entity object into a managed object, rather it can pull straight from the property, which speeds up comparisons drastically.

Kinetic Stack
  • 788
  • 12
  • 33
  • Yeah, that would be an option, except that I really need all those objects. It's a table that is displaying all departments from which the user can then pick one. If there really is no way around the speed thing, I'll have to change that, but for now I'd like to see if there is anything else I could do to keep my current UI with speeds less than a second. – Scott Berrevoets Dec 24 '11 at 12:33
0

When you're inserting Department objects into the context, are you saving for each object? Inserting is relatively cheap, but saving (i.e. -[NSManagedobjectContext save:]) is expensive (since the database has to do locking and file I/O).

Also, on a more stylistic note, you can do

    for (NSDictionary *element in informationElements) {
        NSString *informationValue = [element objectForKey:@"value"];

        if ([[element objectForKey:@"description"] isEqualToString:@"phone"]) {
            department.phone = informationValue;
        } else if ([[element objectForKey:@"description"] isEqualToString:@"email"]) {
            department.email = informationValue;
        } else if ([[element objectForKey:@"description"] isEqualToString:@"web"]) {
            department.website = informationValue;
        }
    }   

to iterate through your array of dictionaries.

Daniel Eggert
  • 6,665
  • 2
  • 25
  • 41
  • 1
    I actually relied on UIDocument's auto-saving mechanism, so I wasn't saving at all. Even with a manual save, my data isn't stored between view switches. I was also aware of the fast enumeration, but since my current method was used in the lecture also, I figured I'd just use that. – Scott Berrevoets Dec 29 '11 at 04:17