Best practice to invalidate cache data after application updated - ios

I have an iOS application and release it two times. For better understanding, let's refer to the first release as V1 and second release as V2.
There is a UITableView display a list of events fetched from the server. For better user experience and performance I will cache the events download from the server. So next time the user enters the app I can show a list of events immediately with cached data instead of a loading indicator.
V1 app: The table view cell is configured with an event which is an NSDictionay and get the name property like this:
// Get the event name
NSString *name = [event objectForKey:#"name"];
V2 app: After event getting more complicated I decide to create a dedicated model for the event, let's call it Event and get the name property like this:
// Get the event name directly from Event model name property
NSString *name = event.name
After releasing V2, here comes the problem.
This V2 app crashes because it tries to read the name property from an event dictionary which is cached by V1 app
My question is what strategy or best practice to invalidate cached data. Some methods come to my mind but they all have flaws.
1) Clean all cached data on every release.
This is the most safer way no matter how our program differs from last version app. But throw away all the cached data may be overreacting.
2) Write more code to check if need to ignore cached data.
Taking the V2 app of mine as an example. I may write code to check the cached event data like this to ensure everything is fine.
// Validation code in V2 app
if ([cachedEvent isKindOfClass:Event.calss]) {
NSString *name = cached.name;
// other code
} else {
// Ignore the cached event and fetch event from server
}
This method works but sometimes isKindOfClass is not enough, you have to write more and more validation code. I can see it will become very troublesome to check every cached data.
Love to hear your thoughts and ways to do it, Thanks in advance.

Related

Multi-threading with core data and API requests

Intro
I've read alot of tutorials and articles on Core Data concurrency, but I'm having an issue that is not often covered, or covered in a real-world way that I am hoping someone can help with. I've checked the related questions in SO and none give an answer to this particular question that I can find.
Background
We have an existing application which fetches data from an API (in the background thread) and then saves the records returned into core data. We also need to display these records in the application at the time.
So the process we currently go through is to:
Make a network request for data (background)
Parse the data and map the objects to NSManagedObjects and save (background)
In the completion handler (main thread) we fetch records from core data with the same order and limit that we requested from the API.
Most tutorials on core data concurrency follow this pattern of saving in one thread and then fetching in another, but most of them give examples like:
NSArray *listOfPeople = ...;
[NSManagedObjectHelper saveDataInBackgroundWithContext:^(NSManagedObjectContext *localContext){
for (NSDictionary *personInfo in listOfPeople)
{
PersonEntity *person = [PersonEntity createInContext:localContext];
[person setValuesForKeysWithDictionary:personInfo];
}
} completion:^{
self.people = [PersonEntity findAll];
}];
Source
So regardless of the amount of records you get back, you just fetch all content. This works for small datasets, but I want to be more efficient. I've read many times not to read/write data across threads, so fetching afterwards gets around this issue, but I don't want to fetch all, I just want the new records.
My Problem
So, for my real world example. I want to make a request to my API for the latest information (maybe anything older than my oldest record in core data) and save it, them I need the exact data returned from the API in the main thread ready for display.
So my question is, When I reach my completion handler, how do I know what to fetch? or what did the API return?. A couple of methods I've considered so far:
after saving each record, store the ID in a temporary array and then perform some fetch where id IN array_of_ids.
If I am asking for the latest records, I could just use the count of records returned, then use an order by and limit in my request to the latest x records.
My Question
I realize that the above could be answering my own question but I want to know if there is a better way, or is one of those methods much better to use than the other? I just have this feeling that I am missing something
Thanks
EDIT:
Neither answer below actually addresses the question, This is to do with fetching and saving data in the background and then using the returned data in the main thread. I know it's not a good idea to pass data between threads, so the common way around this is to fetch from core data after inserting. I want to work out the more efficient way.
Have you checked NSFetchedResultsController? Instead of fetching presented objects into array, you will use fetched controller in similar fashion. Through NSFetchedResultsControllerDelegate you would be notified about all the changes performed in background (rows added, removed, changed) and no manual tracking would be needed.
I feel You missing case with two silmultaneous API calls. Both storring ids and counting created enities wont work for that case. Consider adding timestamp property for each PersonEntity.
Assuming that Your intention is to display recently updated persons.
The calcutation of the oldest timestamp to display can look like this:
#property NSDate *lastViewRefreshTime;
#property NSDate *oldestEntityToDisplay;
(...)
if (self.lastViewRefreshTime.timeIntervalSinceNow < -3) {
self.oldestEntityToDisplay = self.lastViewRefreshTime;
}
self.lastViewRefreshTime = [NSDate date];
[self displayPersonsAddedAfter: self.oldestEntityToDisplay];
Now, if two API responses returns in period shorter than 3s their data will be displayed together.

Extract specific update from NSPersistentStoreCoordinator

I'm currently using iCloud and CoreData to sync data across my app, so every time notification fires, I update my local array of data. The problem I am running into is that my data set is getting large and I don't want to update the entire set of data every time there is a new notification.
Basically, I have an Entity called Photo, and every time the user makes an update to one Photo object on device A, it gets synced with iCloud, which then gets pushed to device B. The device receives the notification through:
persistentStoreDidImportUbiquitousContentChanges:
which looks like this:
notification.userInfo.description:
{
deleted = "{(\n)}";
inserted = "{(\n 0x17045c80 56E70CB19352/Photo/p8431>\n)}";
updated = "{(\n)}";
}
I'd like to grab that specific insertion, update, or deletion and apply it to my local array instead of iterating through the entire set of fetchedObjects.
I tried casting the insertion object to a Photo object, but that didn't work. Any thoughts on how to extract that info?
Thanks!
The objects in that notification are instances of NSManagedObjectID. You can use those with your NSManagedObjectContext to retrieve the managed objects. Use existingObjectWithID:error: (safe, potentially slow) or objectWithID: (fast, potentially less safe).
You probably don't need to do that, though. You can take that notification and pass it to mergeChangesFromContextDidSaveNotification: to merge any changes it contains. If you need to do manual merging, you can, but it's usually not needed.

How to avoid calling a method multiple times when the method takes long time to complete

There are several view controllers in my app where I need to sync the local contents with server using a method running in a background thread. Sometimes I need to insert data to my database on server if user has created any. The approach I am using here is to set a flag(something like isSynced = NO) on objects that I need to sync with server (there objects are in Core Data). When the syncing is complete my method will get rid of the flag(e.g. isSynced = YES) so it won't be sent again next time.
Now the problems is that the syncing method takes very long to complete(1 or 2seconds.). If now user pops out this particular view controller and swiftly comes back the previous call is still in progress and next one will be kicked off. The consequence is that there might be duplication in database.
My approach now is the make the syncing method to be called by a Singleton object:
#property (nonatomic) BOOL isSyncing;
//every time before syncing. check if object is available for syncing
if (!isSyncing) {
isSyncing = YES;
// sync server
// when complete
isSyncing = NO;
// post notification to view controller to reload table
} else {
// cancel because previous call is not finished
}
My concern is that if the call is cancelled my view controller will not be able to receive the notification is waiting for. I can fix this by posting another notification in the event of cancelation. I am wondering if this is the right to do this because I think that this problem should be pretty common in iOS development and there should be a standard way to deal with it
Your singleton approach may not be necessary. I don't see the harm in sending a database insert for each new object. You will still need to ensure each object is synched. That is, update the "isSynched" flag. Keep each object that needs to be synced in a "need to synch" list.
Then, update the "isSynced" flag by performing a background query on the database to check if the object exits. Then, use the result of the query to set the isSynched flag.
If the query result indicates the object is not in the database you then resend the object and leave it's "isSynced" flag set to NO.
If the query result indicates the object is in the database, set the "isSynced" flag to YES and remove it from your "need to synch" list.
An approach for preventing duplicate database entries is to make a unique key. For example, tag each with a hash based on the time and date. Then configure the table to ensure each key is unique.

Cancelling NSJSONSerialization - Search as you type, requests overlapping

Similar to the iPhone Facebook app search function, I am implementing search as you type functionality into my application although I have a problem when decoding the data into JSON format.
Basically what happens is because some searches take longer than others, they return at different intervals and this causes some small visual issues when the data is presenting on the screen.
I have set an NSLOG after each decode using NSJSONSerialization for the keyword 'industry'
2013-04-09 23:38:18.941 Project Name [42836:1d03] http://fooWebAddress/json/?method=search&limit=10&q=indus
2013-04-09 23:38:19.776 Project Name [42836:3e07] http://fooWebAddress/json/?method=search&limit=10&q=indu
2013-04-09 23:38:20.352 Project Name [42836:8803] http://fooWebAddress/json/?method=search&limit=10&q=indust
2013-04-09 23:38:21.814 Project Name [42836:4e03] http://fooWebAddress/json/?method=search&limit=10&q=industr
2013-04-09 23:38:23.434 Project Name [42836:8803] http://fooWebAddress/json/?method=search&limit=10&q=ind
2013-04-09 23:38:24.070 Project Name [42836:7503] http://fooWebAddress/json/?method=search&limit=10&q=industry
As you can see it is all out of order.
Does anyone have any way of stopping NSJSONSerialization for the previous connection.
Or possibly any other way to go about this problem?
Steps up to NSJSONSerialization...
NSURLRequest (initwithURL)
NSOperationQueue
NSURLConnection (asynchronous)
NSJSONSerialization
Thanks in advance.
When the user starts typing more text, you could cancel your previous connections and ignore any further delegate callbacks you receive from them. Then make the new request for the current text.
You can do this by maintaining some sort of lastRequest or lastOperation reference. When the user starts typing, call [self.lastRequestOrOperation cancel] and ignore any further notifications from that request with a check like if (request != self.lastRequest) { return; } in whatever callbacks you have.
However this has the problem that if the user keeps typing for a while you are constantly cancelling requests and they may not see any results until they have stopped typing.
A better solution would be to add sequencing so that each request is associated with an increasing sequence ID. You then only parse the result and update the UI when the sequence of the response is higher than the last one you received. If you receive any out-of-band responses from earlier, you just ignore them.
This is a much more complex issue than just being able to cancel the NSJSONSerialization. My suggestion is to use NSFetchedResultsController to populate your table view that shows the search results. Use the search term as one of the predicate variable in the NSFetchRequest attached to NSFetchedResultsController. And then, when you parse the results using NSJSONSerialization, store the results with the search term associated with that request. As soon as the search term changed (which you can detect when the user types more characters), re-create the NSFetchedResultsController and reload your table view. In addition, you can also try to cancel the call to parse the previous results if you launched it using performSelector:withObject:afterDelay. Beware that this cannot be always relied upon as the call may have been initiated by the time you are trying to cancel.
Kinda basic, but you could always maintain an nsdictionary of sub-classed NSURLRequests (sub-classed to provide a tag).
Start request - add request to dicationary with tag = array.count - 1, with key matching tag
Connection returns - is the request the most recent request, if so, parse json
Parse JSON - is the request the most recent request, if so, show results, if not, only display if there are no previous results displayed
Request handling - remove key from dictionary
most recent request = does the dictionary contain an object with a higher key value
Currently what you are doing is, you type each character and calling web-service. Why to call web-service for each letter you type. If user is type continuously, then it will increase the load, so call the web-service only when user stops for a particular interval of time. and then pass that string to call web-service or what ever method you are calling.
[NSObject cancelPerformSelectorsWithTarget:self]; // This will cancel your all req which is going to make when user typing without stopping
[self performSelector:#selector(sendSearchRequest) withObject:searchText afterDelay:0.1f]; // This will pass the string to call a web-service method, on which user hold for some time.

NSUserDefaults transactionality

Is there any way to add transactionality to NSUserDefaults? I would need something like the well known begin - commit - revert functions on database handlers, thus I could revert a modification on the user defaults in some cases. Of course other users of this user defaults would be blocked from writing during the transaction.
Note1: synchronize method of the above class does not do this thing because:
according to the doc, it is called from time to time also by the framework
there is no "revert"
Note2: I saw dictionaryRepresentation and registerDefaults with that I could implement my own transaction mechanism (holding a copy of the old defaults in the memory / even saved to a plist during the transaction). But maybe there is a ready solution for this?
My use case:
I have a wizard-like flow of screens where the user can edit some settings on each screen. As of the current implementation these settings are stored immediately in the defaults as the user moves to the next screen of the wizard. Now this wizard can be interrupted by some other events (even the user can choose to exit/cancel the wizard at any screen) and in this case I would like to roll back the modifications.
One possible solution is to defer setting the values until the end of your wizard. This can be easily done for example using a proxy that will record the messages sent to it and then replay them on the real NSUserDefaults. Recording the messages should be pretty simple:
- (void) forwardInvocation: (NSInvocation*) invocation
{
[invocations addObject:invocation];
}
Where invocations is a mutable array. Replaying the messages back is also simple:
- (void) replayOnTarget: (id) target
{
for (NSInvocation *op in invocations)
[op invokeWithTarget:target];
}
This way the wizard does not have to know anything about the transactions. It would get the recording proxy instead of the expected NSUserDefaults instance and send the messages as usual. After the calling code knows the wizard succeeded, it can replay the messages from the proxy on the shared user defaults. (I have added some sample code on GitHub.)
Maybe this is overkill, but since the recording proxy is generic and can be used in other cases, maybe it’s not bad. Same thing can also be done using blocks:
[transaction addObject:[^{
[defaults setObject:… forKey:…];
} copy]];
Where transaction is a mutable array, again. When the wizard succeeds, you would simply iterate over the array and execute the stored blocks.

Resources