1

I've been struggling with this for ages now and I really need some good help here. :) I have an app where I'm parsing a quite big JSON into appdelegate's didFinishLaunchingWithOptions.

My Model Objects are:

Tab:

NSString *title
NSMutableArray *categories

Category:

NSString *title
NSMutableArray *items

Item

NSString *title
NSString *description
UIImage *image

I need to save the data locally, cause the parsing takes about 15 seconds every time my app starts. I'm using the SBJSON framework.

Here's my code for parsing:

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"json_template" ofType:@"json"];

    NSString *contents = [NSString stringWithContentsOfFile: filePath  encoding: NSUTF8StringEncoding error: nil];
    SBJsonParser *jsonParser = [[SBJsonParser alloc] init];
    NSMutableDictionary *json = [jsonParser objectWithString: contents];
    tabs = [[NSMutableArray alloc] init];
    jsonParser = nil;

    for (NSString *tab in json)
    {
        Tab *tabObj = [[Tab alloc] init];
        tabObj.title = tab;

        NSDictionary *categoryDict = [[json valueForKey: tabObj.title] objectAtIndex: 0];
        for (NSString *key in categoryDict)
        {

            Category *catObj = [[Category alloc] init];
            catObj.name = key;


            NSArray *items = [categoryDict objectForKey:key];

            for (NSDictionary *dict in items)
            {
                Item *item = [[Item alloc] init];
                item.title = [dict objectForKey: @"title"];
                item.desc = [dict objectForKey: @"description"];
                item.url = [dict objectForKey: @"url"];
                if([dict objectForKey: @"image"] != [NSNull null])
                {
                    NSURL *imgUrl = [NSURL URLWithString: [dict objectForKey: @"image"]];
                    NSData *imageData = [NSData dataWithContentsOfURL: imgUrl];
                    item.image = [UIImage imageWithData: imageData];
                }
                else
                {
                    UIImage *image = [UIImage imageNamed: @"standard.png"];
                    item.image = image;
                }

                [catObj.items addObject: item];   
            } 
            [tabObj.categories addObject: catObj];
        } 
        [tabs addObject: tabObj];
    }

What is the best way of doing this? Using Core Data or NSFileManager? If you have som code example too it will make me very happy. This is the last thing i need to fix before the app is ready for app store and it just kills me! I can't solve this problem.

Jojo
  • 490
  • 1
  • 7
  • 22
  • 1
    What is quiet big for you? 5KB? 10MB? 1GB? – rekire Apr 28 '13 at 13:55
  • have you ever considered running [Xcode instruments](http://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/Introduction/Introduction.html) on your code? I see stuff in that parsing that would certainly slow things down dramatically (e.g. the synchronous "`NSData dataWithContentsOfURL`" calls). – Michael Dautermann Apr 28 '13 at 13:58
  • @MichaelDautermann What can I use instead of NSData? – Jojo Apr 28 '13 at 14:10
  • If you want to save a large (at least 10s of kbytes) JSON string then the simplest/fastest way would be as a flat file. If you want to save the pieces of the parsed JSON then Core Data or your own SQLite DB. – Hot Licks Apr 28 '13 at 14:48
  • While SBJson is quite slow in parsing, 15sec is not normal. This would correspond to a JSON with roughly 20 MByte (iPad 2 speed). With NSJSONSerialization your 100kB UTF-8 data should be decoded in 12 ms. However, the _code within the for loop_, where you create the images should be processed on a secondary thread or a dispatch queue. Note: since iOS 4.0 many methods regarding images and drawing context became thread-safe. Additionally, your image URL is certainly a file URL, isn't it? – CouchDeveloper Apr 28 '13 at 20:38
  • Yea, I've made a secondary thread for each NSDataWithContent. And now it takes about two seconds for the JSON to load! Much better! But now my problem is that you can click on a tab before all the images are loaded, and it will show an tableview without a picture. How can i check if every image is loaded? – Jojo Apr 29 '13 at 07:31
  • After the image loads refresh the view. – Hot Licks Apr 30 '13 at 00:11

1 Answers1

2

If you are working on iOS then you save a file to the Documents folder. On Mac OS X it would be in the Application Support folder. Since you are on iOS, read this answer for how to access the Documents folder.

All of the objects that you want to store should implement NSCoding. The above variables already do. Should you want to store the tabs, categories and items directly they would need to implement NSCoding. Then all you need is to serialize them to a file. When opening you app you can look for this file and get your objects back without parsing.

The code should look something like this (untested and error checking is ommited for brevity):

- (void) saveStateToDocumentNamed:(NSString*)docName
{
    NSError       *error;
    NSFileManager *fileMan = [NSFileManager defaultManager];
    NSArray       *paths   = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString      *docPath = [paths[0] stringByAppendingPathComponent:docName];

    if ([fileMan fileExistsAtPath:docPath])
        [fileMan removeItemAtPath:docPath error:&error];

    // Create the dictionary with all the stuff you want to store locally
    NSDictionary *state = @{ ... };

    // There are many ways to write the state to a file. This is the simplest
    // but lacks error checking and recovery options.
    [NSKeyedArchiver archiveRootObject:state toFile:docPath];
}

- (NSDictionary*) stateFromDocumentNamed:(NSString*)docName
{
    NSError       *error;
    NSFileManager *fileMan = [NSFileManager defaultManager];
    NSArray       *paths   = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString      *docPath = [paths[0] stringByAppendingPathComponent:docName];

    if ([fileMan fileExistsAtPath:docPath])
        return [NSKeyedUnarchiver unarchiveObjectWithFile:docPath];

    return nil;
}
Community
  • 1
  • 1
aLevelOfIndirection
  • 3,522
  • 14
  • 18