Reading from unsaved plist

Discussion in 'iOS Programming' started by SimonBS, Mar 9, 2011.

  1. SimonBS macrumors regular

    SimonBS

    Joined:
    Dec 30, 2009
    #1
    Hi,

    I am developing an application which retrieves its data from a plist located on my webserver. Therefore the application is dependent on a network connection. I want the user to be able to use my application when offline and therefore I am saving a copy of the plist on the users device every time the application is loaded with network connection. From there, I read the data from the plist located on the device.

    I am having troubles, though. I am starting the download of the data in didFinishLaunchingWithOptions method of the AppDelegate. This is done like this:

    Code:
    if(hasConnection) { // returns TRUE or FALSE based on Apple's example on checking network reachability
            NSLog(@"Starting download of data.");
    
            // loading using NSURLConnection
            NSURLRequest *theRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:FETCH_URL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
    
            // create the connection with the request
            // and start loading the data
            NSURLConnection *theConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
            if (theConnection) {
                // Create the NSMutableData to hold the received data.
                // receivedData is an instance variable declared elsewhere.
                receivedData = [[NSMutableData data] retain];
            }
        }
    Then I add the data to my plist (Bands.plist) in connectionDidFinishLoading

    Code:
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        // throw data into a property list (plist)
        NSMutableArray *tmpArray = [NSPropertyListSerialization propertyListFromData:receivedData mutabilityOption:NSPropertyListMutableContainers format:nil errorDescription:nil];
    
        NSMutableArray *plistArray = [[NSMutableArray alloc] initWithArray:tmpArray];
    
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
    
        NSString *path = [documentsDirectory stringByAppendingPathComponent:@"Bands.plist"];
    
        [plistArray writeToFile:path atomically:YES];
        [plistArray release];
    
        // release the connection, and the data object
        [connection release];
        [receivedData release];
    }
    But when loading the application for the first time, it crashes. I believe this is due to the application trying to reach this data even though it is not yet saved. If I remove the part that tries to reach the data saved locally, I have no problem. Neither do I have a problem if I add it again and restart the application (second load).

    Does anyone have an idea how to fix this issue?

    It is as if my application tries to load and handle data which does not yet exist.
     
  2. ulbador macrumors 68000

    ulbador

    Joined:
    Feb 11, 2010
    #2
    Without seeing more of the code, I can't answer what's wrong.

    It seems pretty clear you know where it's crashing. If that's the case, just make darn sure you are checking that all your data is valid at that point before you try to use it. Otherwise, read up on how to use the debugger to set up some breakpoints, so you can see exactly where it is crashing.
     
  3. balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
    #3
    Can you ship the app with a dummy plist that will satisfy it as existing for the first run.

    B
     
  4. SimonBS thread starter macrumors regular

    SimonBS

    Joined:
    Dec 30, 2009
    #4
    Using a dummy seems to be working. I just check if Bands.plist is in the Documents directory, if not, then the "original" Bands.plist is copied to Documents.

    I am experiencing another problem, though. This is related to the same question.

    My problem is, that if the data has been updated and an old version of Bands.plist is stored on the device, then my application starts reading from Bands.plist while updating and the data becomes a mix of old and new data which causes the application to crash. I can't figure out how to fix this issue and hope that someone has an idea.

    I have an image which illustrates my issue:

    [​IMG]

    What happens is, that my view has a subview which has a page controller. Each page contains a table view where the data is loaded into.

    Here you see that I am sliding from one view to another. In the first view (the left) is the old data and in the second view (the right) is the new data. In this transition of views, the application crashes.

    Can anyone help me figure this out? It seems that the application starts loading the data before it is saved.

    Here is some code. Please say, if more code will help. I can't figure out how to fix this issue.

    This is from the App Delegate. Here the data is retrieved from the webserver and is saved to Bands.plist.

    Code:
    NSLog(@"[AppDelegate] Data has been downloaded.");
        	
    // throw data into a property list (plist)
    NSMutableArray *tmpArray = [NSPropertyListSerialization propertyListFromData:receivedData mutabilityOption:NSPropertyListMutableContainers format:nil errorDescription:nil];
        
    NSMutableArray *plistArray = [[NSMutableArray alloc] initWithArray:tmpArray];
        	
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
        	
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"Bands.plist"];
        	
    NSLog(@"[AppDelegate] Bands.plist has been populated.");
    This is from the table view. Here the data is loaded from Bands.plist.

    Code:
    pageNumber = page;
            		
    NSLog(@"[TableView] Loading view.");
            			
    NSArray *whatDays = [[NSArray alloc] initWithObjects:@"Onsdag", @"Torsdag", @"Fredag", @"Lørdag", @"Ikke programsat", nil];
            			
    sections = [[NSMutableDictionary alloc] init];
            		
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"Bands.plist"];
    tmpArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
            			
    // scan through data and sort
    for(int i = 0; i < [whatDays count]; i++) {
         NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.weekDay == %@", [whatDays objectAtIndex:i]];
         NSArray *bandsThisDay = [tmpArray filteredArrayUsingPredicate:predicate];
         [sections setObject:bandsThisDay forKey:[whatDays objectAtIndex:i]];
    }
            			
    [whatDays release];
    I hope that someone can help my fix this issue as I really can't figure out how to solve it.

    Would it be possible to call a method in tableview implementation from the app delegate and tell it to populate the table? So it's waiting for the download to be done?
     
  5. balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
    #5
    Can you post the console log for when that happens?

    Are you actually seeing something like:

    Code:
    [AppDelegate] Data has been downloaded.
    [TableView] Loading view.
    [AppDelegate] Bands.plist has been populated.
    B
     
  6. SimonBS thread starter macrumors regular

    SimonBS

    Joined:
    Dec 30, 2009
    #6
    A successfull load is like this:

    Code:
    2011-03-10 17:26:20.907 NibeFestival[237:707] [AppDelegate] File does not exist.
    2011-03-10 17:26:20.920 NibeFestival[237:707] /var/mobile/Applications/B3D56373-0885-401C-ACCA-86EA057EE816/NibeFestival.app/Bands.plist
    2011-03-10 17:26:20.954 NibeFestival[237:707] [AppDelegate] Bands.plist has been copied to Documents.
    2011-03-10 17:26:21.008 NibeFestival[237:707] [AppDelegate] Starting download of data.
    2011-03-10 17:26:21.087 NibeFestival[237:707] Loading program view.
    2011-03-10 17:26:21.092 NibeFestival[237:707] Initializing
    2011-03-10 17:26:21.098 NibeFestival[237:707] [TableView] File exists!
    2011-03-10 17:26:21.102 NibeFestival[237:707] [TableView] Loading view.
    2011-03-10 17:26:21.149 NibeFestival[237:707] Initializing
    2011-03-10 17:26:21.155 NibeFestival[237:707] [TableView] File exists!
    2011-03-10 17:26:21.159 NibeFestival[237:707] [TableView] Loading view.
    2011-03-10 17:26:21.612 NibeFestival[237:707] Reachability Flag Status: -R -----l- networkStatusForFlags
    2011-03-10 17:26:21.613 NibeFestival[237:707] Has connection.
    2011-03-10 17:26:21.615 NibeFestival[237:707] Reachability Flag Status: -R ------- networkStatusForFlags
    2011-03-10 17:26:21.616 NibeFestival[237:707] Has connection.
    2011-03-10 17:26:21.810 NibeFestival[237:707] [AppDelegate] Appending data.
    2011-03-10 17:26:21.815 NibeFestival[237:707] [AppDelegate] Appending data.
    2011-03-10 17:26:21.821 NibeFestival[237:707] [AppDelegate] Appending data.
    2011-03-10 17:26:21.824 NibeFestival[237:707] [AppDelegate] Appending data.
    2011-03-10 17:26:21.827 NibeFestival[237:707] [AppDelegate] Data has been downloaded.
    2011-03-10 17:26:21.842 NibeFestival[237:707] [AppDelegate] Bands.plist has been populated.
    2011-03-10 17:26:27.619 NibeFestival[237:707] Initializing
    2011-03-10 17:26:27.624 NibeFestival[237:707] [TableView] File exists!
    2011-03-10 17:26:27.625 NibeFestival[237:707] [TableView] Loading view.
    2011-03-10 17:26:31.055 NibeFestival[237:707] Initializing
    2011-03-10 17:26:31.058 NibeFestival[237:707] [TableView] File exists!
    2011-03-10 17:26:31.060 NibeFestival[237:707] [TableView] Loading view.
    2011-03-10 17:26:32.020 NibeFestival[237:707] Initializing
    2011-03-10 17:26:32.024 NibeFestival[237:707] [TableView] File exists!
    2011-03-10 17:26:32.026 NibeFestival[237:707] [TableView] Loading view.
    Then I update the data and a crash looks like this:

    Code:
    2011-03-10 17:29:08.146 NibeFestival[257:707] [AppDelegate] File exists!
    2011-03-10 17:29:08.196 NibeFestival[257:707] [AppDelegate] Starting download of data.
    2011-03-10 17:29:08.243 NibeFestival[257:707] Loading program view.
    2011-03-10 17:29:08.248 NibeFestival[257:707] Initializing
    2011-03-10 17:29:08.253 NibeFestival[257:707] [TableView] File exists!
    2011-03-10 17:29:08.257 NibeFestival[257:707] [TableView] Loading view.
    2011-03-10 17:29:08.296 NibeFestival[257:707] Initializing
    2011-03-10 17:29:08.301 NibeFestival[257:707] [TableView] File exists!
    2011-03-10 17:29:08.305 NibeFestival[257:707] [TableView] Loading view.
    2011-03-10 17:29:08.758 NibeFestival[257:707] Reachability Flag Status: -R -----l- networkStatusForFlags
    2011-03-10 17:29:08.760 NibeFestival[257:707] Has connection.
    2011-03-10 17:29:08.761 NibeFestival[257:707] Reachability Flag Status: -R ------- networkStatusForFlags
    2011-03-10 17:29:08.762 NibeFestival[257:707] Has connection.
    2011-03-10 17:29:08.934 NibeFestival[257:707] [AppDelegate] Appending data.
    2011-03-10 17:29:08.939 NibeFestival[257:707] [AppDelegate] Data has been downloaded.
    2011-03-10 17:29:08.942 NibeFestival[257:707] [AppDelegate] Bands.plist has been populated.
    2011-03-10 17:29:19.056 NibeFestival[257:707] Initializing
    2011-03-10 17:29:19.061 NibeFestival[257:707] [TableView] File exists!
    2011-03-10 17:29:19.062 NibeFestival[257:707] [TableView] Loading view.
    2011-03-10 17:29:20.805 NibeFestival[257:707] Initializing
    2011-03-10 17:29:20.809 NibeFestival[257:707] [TableView] File exists!
    2011-03-10 17:29:20.811 NibeFestival[257:707] [TableView] Loading view.
    2011-03-10 17:29:20.835 NibeFestival[257:707] *** Assertion failure in -[UITableView _createPreparedCellForGlobalRow:withIndexPath:], /SourceCache/UIKit/UIKit-1448.89/UITableView.m:5678
    2011-03-10 17:29:20.859 NibeFestival[257:707] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'
    *** Call stack at first throw:
    (
    	0   CoreFoundation                      0x345b164f __exceptionPreprocess + 114
    	1   libobjc.A.dylib                     0x35586c5d objc_exception_throw + 24
    	2   CoreFoundation                      0x345b1491 +[NSException raise:format:arguments:] + 68
    	3   Foundation                          0x30dad573 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 62
    	4   UIKit                               0x34d05a89 -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:withIndexPath:] + 672
    	5   UIKit                               0x34d0576b -[UITableView(UITableViewInternal) _createPreparedCellForGlobalRow:] + 34
    	6   UIKit                               0x34cfe0cd -[UITableView(_UITableViewPrivate) _updateVisibleCellsNow:] + 936
    	7   UIKit                               0x34cfd27d -[UITableView layoutSubviews] + 140
    	8   UIKit                               0x34ca95fb -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 26
    	9   CoreFoundation                      0x3451ef03 -[NSObject(NSObject) performSelector:withObject:] + 22
    	10  QuartzCore                          0x30286bb5 -[CALayer layoutSublayers] + 120
    	11  QuartzCore                          0x3028696d CALayerLayoutIfNeeded + 184
    	12  QuartzCore                          0x3028c1c5 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 212
    	13  QuartzCore                          0x3028bfd7 _ZN2CA11Transaction6commitEv + 190
    	14  QuartzCore                          0x30285055 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 56
    	15  CoreFoundation                      0x34588a35 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 16
    	16  CoreFoundation                      0x3458a465 __CFRunLoopDoObservers + 412
    	17  CoreFoundation                      0x3458b75b __CFRunLoopRun + 854
    	18  CoreFoundation                      0x3451bec3 CFRunLoopRunSpecific + 230
    	19  CoreFoundation                      0x3451bdcb CFRunLoopRunInMode + 58
    	20  GraphicsServices                    0x319bb41f GSEventRunModal + 114
    	21  GraphicsServices                    0x319bb4cb GSEventRun + 62
    	22  UIKit                               0x34cd2d69 -[UIApplication _run] + 404
    	23  UIKit                               0x34cd0807 UIApplicationMain + 670
    	24  NibeFestival                        0x000022f7 main + 70
    	25  NibeFestival                        0x000022ac start + 40
    )
    terminate called after throwing an instance of 'NSException'
    Program received signal:  “SIGABRT”.
    When it says "[TableView] Loading view." is obviously everytime I change the view in the page view controller and has it loading the data for the view.

    You see that in the third view (where the new data is) it says "Loading data" and crashes.
     
  7. balamw Moderator

    balamw

    Staff Member

    Joined:
    Aug 16, 2005
    Location:
    New England
  8. SimonBS thread starter macrumors regular

    SimonBS

    Joined:
    Dec 30, 2009
    #8
    They are both released in the dealloc.

    Code:
    - (void)dealloc {
    	[tableProgram release];
    	[days release];
    	[thisDay release];
    	[B][tmpArray release];
    	[sections release];[/B]
    	[navController release];
    	[headerImage release];
        [super dealloc];
    }
    I don't think this is a problem.
     
  9. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #9
    Review the code for your tableView:cellForRowAtIndexPath: method. Somehow you're not returning a cell in some circumstance.
     
  10. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #10

    I've hilited peculiar log outputs in red. Notice how they appear in pairs, only a few milliseconds apart.

    The blue-hilited one is the lone exception. It doesn't have a time near the others, nor is there any doubling.

    Doubling strikes me as potentially harmful, especially if there's no apparent reason for it, and the code path has side effects such as creating or mutating objects. Uncontrolled threaded access may prove especially harmful in such cases.

    The doubled items being only a few milliseconds apart suggests some kind of race condition, or possibly multiple objects where only one should exist. Those are just guesses. It's difficult to diagnose or debug with only the code fragments posted.
     
  11. SimonBS thread starter macrumors regular

    SimonBS

    Joined:
    Dec 30, 2009
    #11
    You are right. That does indeed look.. Ugly. I really have to take a closer look on that. I'm sure it's my bad code. I'm quite new to Objective-C (and iOS development of course) and I think I just haven't figured out how to do it with less requests and optimize my code as much as possible. It is certainly something that I will have to look into!

    Thank you for pointing that out. Actually I had seen that but I didn't really give it a thought as I was pretty sure the issue was something else. It seems to (almost) work now, though. I rewrote the method and now it isn't crashing.

    This leads me to another issue though. The first two (out of five table views in the page controller) are loaded when the app starts up. It's like Bands.plist is not updated yet at this time and therefore these two views shows old information while the three others are updated.

    Does anyone have an idea how to fix this? To "pause" the loading for a bit or something similar? As it is now, people are actually never sure that the information they see is the latest.
     
  12. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #12
    How about putting up a "Refreshing Data..." indicator (complete with UIActivityIndicatorView) as the new data gets loaded. Similar to Apple's "Loading..." message.
     
  13. SimonBS thread starter macrumors regular

    SimonBS

    Joined:
    Dec 30, 2009
    #13
    That's a good idea. So I imagine I would make a method in my table view controller which populates the table view and is called from the app delegate when the app delegate is done refreshing my property list.

    Is it possible to call a method in another view from the app delegate? Honestly I thought I would just write the method, import the header file and call it like this
    Code:
    [myViewController myMethod];
    but that does not seem to work.
     
  14. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #14
    Yes, it's possible. Doesn't seem to work how / why? Can you be more specific? Any errors / warnings? Does it crash? Etc. Plus, did you make myMethod public?
     
  15. SimonBS thread starter macrumors regular

    SimonBS

    Joined:
    Dec 30, 2009
    #15
    My method is defined like this:

    The header:
    Code:
    - (void)refreshTableView;
    The implementation:
    Code:
    - (void)refreshTableView {
        NSLog(@"Amazing! This awesome method has been called.");
    }
    In my AppDelegate.m I have added #import "NewProgramTableViewController.h".
    Then I try to call refreshTableView via [NewProgramTableViewController refreshTableView]; but I get the following warnings and if I try to run the app, then it crashes.

    The two warnings are:
    Code:
    Method 'NewProgramTableViewController' not found (return type defaults to 'id')
    Code:
    'NewProgramTableViewController' may not respond to '+refreshTableView'
    I have not done anything to make the method public. How is this done? I thought only variables could be public.
     
  16. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #16
    You've declared refreshTableView as an instance method but then try to call it as a class method; NewProgramTableViewController is a class name not a object instance. If you're not comfortable with how these are different, perhaps it's time to step away from the real coding and (re)learn the fundamentals of Objective-C programming.

    By declaring the method in your header file, you've made it public.
     

Share This Page