Cancelling NSJSONSerialization - Search as you type, requests overlapping - ios

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.

Related

Multiple objects waiting for the same API response

I have an API code, which loads a data necessary for my application.
It's as simple as:
- (void) getDataForKey:(NSString*) key onSuccess:(id (^)())completionBlock
I cache data returned from server, so next calls of that functions should not do network request, until there is some data missing for given key, then I need to load it again from server side.
Everything was okey as long as I had one request per screen, but right now I have a case where I need to do that for every cell on one screen.
Problem is my caching doesn't work because before the response comes in from the first one, 5-6 more are created at the same time.
What could be a solution here to not create multiple network request and make other calls waiting for the first one ?
You can try to make a RequestManager class. Use dictionary to cache the requesting request.
If the next request is the same type as first one, don't make a new request but return the first one. If you choose this solution, you need to manager a completionBlock list then you will be able to send result to all requesters.
If the next request is the same type as first one, waiting in another thread until the first one done. Then make a new request, you API will read cache automatically. Your must make sure your codes are thread-safe.
Or you can use operation queues to do this. Some documents:
Apple: Operation Queues
Soheil Azarpour: How To Use NSOperations and NSOperationQueues
May be there will be so many time consuming solutions for this. I have a trick. Create a BOOL in AppDelegate, its default is FALSE. When you receive first response, then set it TRUE. So when you go to other screen and before making request just check value of your BOOL variable in if condition. If its TRUE means response received so go for it otherwise in else don't do anything.

Storing a set of parameters in a dictionary

I have a networking method that provides a friendly interface to my API. Something like:
getWeatherForCities:(NSArray *)cityCodes
startDate:(NSDate *)startDate
endDate:(NSDate *)endDate
useCelcius:(BOOL)useCelcius
maxResults:(NSNumber *)maxResults
This is called multiple times in my app, sometimes concurrently with different parameters. There is also a completion and failure block but they aren't needed here.
I would like to add an option that tells the method only to execute the completion block if the data is different to the last time it was requested with the same parameters. This way some consumers can say that they want to know everything, and others can ask only for data if it is new.
It seems like I need some way to store a representation of all the parameters, alongside the last received response for those parameters. I would love to do this in an NSDictionary, but am open to other ideas. Is there some way to convert the parameters into a unique key? Or some better solution?
I am currently using the [dictionary description] as the key.

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.

Storing a URL that your app points to externally, so URL be changed anytime

I have a UIImageView - *bannerImageView in my application which makes use of AFNetworking's convenient - (void)setImageWithURLRequest:placeholderImage:success:failure: method.
To prepare for usage of this method, I have to create an NSURLRequest object as follows:
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.images.com/image.png"]];
QUESTION:
Is there a way that I can store this url (I'm thinking in a text file?) somewhere online, that my app will always point to to look for a URL to request?
Reason being that due to certain cirumstances of the project I'm developing for, I will need to change the image's URL in future rather frequently and it doesn't make sense to have to push a whole app update just for this purpose.
P.S. In my - (void)viewDidLoad, I have a line of code that starts the networkActivityIndicator - [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;. Is there a better place for me to insert this code rather than to start the networkActivityIndicator each time the view loads, and only stop it in the success block of the - (void)setImageWithURLRequest:placeholderImage:success:failure: method?
Thank you for you help!
If I understood correctly, the logo has to be changed globally for all users. If so, and if you have full control on server side you have two options:
using php or whatever server side language, create a very minimal proxy that transparently map a generic image name to the proper resource, this require little or no code on iOS side, and more on server side. For minimal proxy, I mean, you call a resource, let's say http://www.yourserver.com/imagelocator.php which is retrieving your image in background, and then serve it to your client by setting the appropriate header. You need to write a few lines of code, depending on the server side language used.
implement a 307 redirect, and manage that with iOS, this require little code on server side, and more code on iOS side. This is similiar to your solution about txt file, but is more elegant in my opinion. If you have apache you can create an .htaccess file with all the rule you need. If you have a look at iOS NSURLConnection you will see that by interpreting the HTTP status code, you can handle a redirection. In this case if iOS ask for http://www.yourserver.com/logo.png the server itself is responding with the new address http://www.thenewserver.com/logo.png
In both cases, remember to make use of proper expiration http header, to let iOS check from time to time.
By the way, as I said, your solution of checking a txt file, it's perfectly valid. But even in this case, remeber to set the expiration header on such http request, maybe with a "no cache".
I have found a rather simple way of achieving this with the help of a mobile BaaS (Backend as a Service). I have decided to use Parse.
Out of the many features available there, I have used the data browser the following is an example of what I've done:
Created a class called Advertisement
Created a string column called AdURL *(and whatever information I need that is relevant to that object e.g. AdName or AdImage etc. with its respective data type)*.
Inserted the URL value into the AdURL column.
Hit enter, and a new object (row) will be created. The object will have a unique ObjectID that will automatically be generated for you.
Now, I just have to integrate the Parse SDK into my project and the query whatever class'es objects/value(s) that I have created above with its respective ObjectID.
In future, if I ever need to change the value (in this case, the URL that will be opened in Safari when the user taps a UIButton), I just have to go back to Parse's data browser to change that particular value, and the changes will be reflected immediately as each time the app needs to look for that value, it creates a new query to that class, object, and value of the specified column.
Here is an example of the query code in my app:
- (IBAction)bannerBtnTapped:(id)sender
{
// Create PFQuery object.
PFQuery *query = [PFQuery queryWithClassName:#"EXAMPLECLASS"];
[query getObjectInBackgroundWithId:#"EXAMPLEOBJECTID" block:^(PFObject *object, NSError *error)
{
// "object" will be the row that is returned according to your specified class and ObjectID. Assigned the returned value to an NSURL var "*url"
NSURL *url = [NSURL URLWithString:object[#"EXAMPLECOLUMNNAME"]];
// Open the url in Safari.
[[UIApplication sharedApplication] openURL:url];
}];
}
Hope this simple solution helps somebody in future!
You can use apple's iCloud to store that URL.
Then , you can track changes using e NSUbiquitousKeyValueStoreDidChangeExternallyNotification . You can check apple's documentation

iOS Core Data "initialization time"?

In my viewWillAppear I execute multiple fetch requests. I noticed that on the first fetch request, nothing is found even though I know items should be found. I tried sleeping the main thread before executing the first fetch request and sure enough items were found. I'm only running on the simulator too btw.
So my question is, does Core Data take some time to "initialize" or anything? Will I have to worry about this when testing on a device (I don't have developer membership yet so I can't test it on a device)? Is there any way to "check" if it is safe to execute a fetch request?
I also checked the NSError for the first fetch request with no delay and it was null.
EDIT:
My viewWillAppear code was requested so here it is. Not very helpful though.
[NSThread sleepForTimeInterval:0.1];
for (NSString *category in self.categories) {
[self updateSection:category];
}
self.cateogires is just my sections of my table view and updateSection: updates the sections.
EDIT2:
I did some further investigating with NSLog statements and here's what I found.
I added NSLog statements to log how many objects are found after a fetch request and also the current state of the document. For some reason I'm getting fetch results even though the document appears to be closed.
Category1 has 3 values
Doc is open? NO
Category2 has 6 values
Doc is open? NO
Category3 has 3 values
Doc is open? NO
Category4 has 2 values
Doc is open? NO
I think my problem is that I am not using the completionHandler of [myManagedDocument openWithCompletionHandler: ] to wait and execute my fetch request. However, why then, do I get fet results if I am querying when the document is closed?
I suggest you try changing the concurrency type to
NSMainQueueConcurrencyType. This will make the operations sequential.

Resources