I want to search the AppStore based on what a user types into search.
I have set up the following code to do this, which will amend the search on each character being entered to narrow the search.
However, as there is a request made as every character entered and these can take time to return, the UI can become unresponsive.
I would like to a) understand how I can stop the UI becoming unresponsive (I fear I am bringing the running back onto the main thread with performselectoronmainthread?), and b) would it be prudent to cancel the previous lookup as each character is entered, thus using the new narrower search, and if so how to do this?
Thanks in advance.
Update: I have tried the suggestion as made by Emilie Lessard, and whilst I can see the logic, I am unable to get this to benefit the app. See response below.
#define kBgQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0)
-(void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)text
{
if(text.length == 0)
{
jsonResults = nil;
[self.tableView reloadData];
}
else
{
jsonResults = nil;
[self.tableView reloadData];
NSURL *searchUrl = [NSURL URLWithString:[NSString stringWithFormat:#"https://itunes.apple.com/search?term=%#&country=gb&entity=software",text]];
dispatch_async(kBgQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:searchUrl];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:NO];
});
}
}
-(void)fetchedData:(NSData *)responseData{
NSError* error;
NSDictionary* json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
jsonResults = [json objectForKey:#"results"];
[self.tableView reloadData];
}
You need to use dispatch_async()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Do the computing-research
dispatch_async(dispatch_get_main_queue(), ^{
//do UI update here
});
});
By using a global queue, the UI of your app won't get blocked. Once all info has been computed/received, it is MANDATORY that you go back to the main thread (by using dispatch_async(dispatch_get_main_queue()) for all UI updates or you'll end up with hard-to-debug crashes.
Related
NSURL *url=[NSURL URLWithString:string];
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(backgroundQueue, ^{
NSData *data=[NSData dataWithContentsOfURL:url];
NSDictionary *json=[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSLog(#"%#",json);
marray=[NSMutableArray array];
for (NSDictionary *dict in json) {
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
Is this the right way to handle data and reload the table in Objective C? If yes, then I still see some delay in seeing the data on tableview. Is there any way I can eliminate this delay? By the way this is second screen in my storyboard.
You are doing it all right. Download the data on background thread and handing it over to table view to reload data in main thread is all what you need to do. It's almost certainly your own delay (the network delay and the parsing time) in providing the data to the table, not the UITabvleView's delay in handling your reloadData call.
Some of the general rules to be followed when doing this:
Show loading overlay on screen when server call is going on.
Once data is returned pass it on to table view on main thread. Remove the loading overlay now.
Do not do heavy stuff in cellForRowAtIndexPath: method.
As a side note, although the same thingy but try once with below if you are following all above guidelines.
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
Im fetching data from database through a php-API with this code:
- (void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self loadDataWithSpinner];
[self reloadAllData];
}
- (void) loadDataWithSpinner {
if (!self.spinner) [self setupSpinnerView];
self.sessions = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
self.userId = [[NSUserDefaults standardUserDefaults] integerForKey:#"CurrentUserId"] ;
self.sessions = [self getAllSessionsForUserId:self.userId];
dispatch_async(dispatch_get_main_queue(), ^(void){
if (self.sessions) {
self.spinnerBgView.hidden = YES;
[self setupChartsAndCountingLabelsWithData];
}
});
});
}
- (NSArray *) getAllSessionsForUserId: (int) userId {
NSData *dataURL = nil;
NSString *strURL = [NSString stringWithFormat:#"http://webapiurl.com/be.php?queryType=fetchAllBasicSessions&user_id=%i", userId];
dataURL = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
NSError *error = nil;
if (dataURL) {
NSArray *sessions = [NSJSONSerialization JSONObjectWithData:dataURL options:kNilOptions error:&error];
return sessions;
} else {
return Nil;
}
}
Is there something wrong with the code? I'm getting the correct data when testing in a web-browser with the same database call. But in the app it sometimes updates straight away, as it should, and sometimes it takes up to five minutes for the data to update. Even though i remove the app from the phone/simulator, the data sometime hasn't been update when opening the app again.
I would really appreciate some help.
I finally found the answer. Its nothing wrong with my code, as I thought. The problem lies on the server side. And, as they say in this thread:
NSURLConnection is returning old data
It seems to be badly configured server-side or proxy. Which makes the cache act all wierd. I tried another server and everything worked just fine!
Hope this helps someone with the same issue, cause it sure wasted me ALOT of time.
I made my search algorithm for my UISearchBar, and I know that I must search in a background thread. Honestly I'm not familiar with multi-threading, so I'm looking for help. I'm using GCD (Grand Central Dispatch).
Here is my code, I want to know is it correct or not.
-(void)mySearchMethod
{
NSArray *allObjects = self.allMyObjects;
__block NSMutableArray *searchArray = [[NSMutableArray alloc] init];
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async( queue, ^{
for (MyObjectClass *myObjectAsDictionary in allObjects) {
for (NSString *titleSubString in [myObjectAsDictionary.title componentsSeparatedByString:#" "]) {
if ([[titleSubString lowercaseString] hasPrefix:[text lowercaseString]]) {
[searchArray addObject: myObjectAsDictionary];
}
}
}
dispatch_async( dispatch_get_main_queue(), ^{
self.tableObjects = searchArray;
[self.myTableView reloadData];
});
});
}
So does this code work in the background, or is it blocking the main thread?
Can the contents of allMyObjects change while the search is running? If so, you have two problems. First problem: you should not accessing an array on one thread while it's being mutated on another. You can fix this simply by copying the array: NSArray *allObjects = [self.allMyObjects copy];. Second problem: your search results might contain objects that have been removed from allMyObjects, or might be missing objects that have been added to allMyObjects. This is a hard problem to solve and how you solve it depends on your app.
What happens if the user cancels the search before your async block finishes running? Will it replace the contents of a table with search results even though the user doesn't want to see the results now?
I am kind of new to web service calls and threading in iOS. I have a ViewController in my app that contains a tableview control. I am populating the table with data obtained via a JSON web service. The JSON web service is called on its own thread, during which I am populating an NSArray and NSDictionary.
My array and dictionary seem like they are going out of scope since my NSLog statement is returning zero for the array count even though while in fetchedData the array is fully populated.
Can someone offer an explanation as to why my array and dictionary objects are empty outside of the thread?
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *serviceEndpoint = [NSString stringWithFormat:
#"http://10.0.1.12:8888/platform/services/_login.php?un=%#&pw=%#&ref=%#",
[self incomingUsername], [self incomingPassword], #"cons"];
NSURL *url = [NSURL URLWithString:serviceEndpoint];
dispatch_async(kBgAdsQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
NSLog(#"ARRAY COUNT: %d\n", [jsonArray count]);
}
-(void)fetchedData:(NSData*)responseData{
NSError *error;
jsonDict = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];
jsonArray = [[jsonDict allKeys]sortedArrayUsingSelector:#selector(compare:)];
for(NSString *s in jsonArray){
NSLog(#"%# = %#\n", s, [jsonDict objectForKey:s]);
}
}
When you use dispatch_async, that bit of code doesn't block. This means your array count log statement triggers before fetchedData is called, so your dictionary and array are still empty. Look at the order of your log statements - you should see array count before your logging of the dictionary.
// Executes on another thread. ViewDidLoad will continue to run.
dispatch_async(kBgAdsQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
// Executes before the other thread has finished fetching the data. Objects are empty.
NSLog(#"ARRAY COUNT: %d\n", [jsonArray count]);
You'll need to finish populating your TableView after the data returns (ie in FetchData:).
The log statement within viewDidLoad SHOULD report that the array is empty as it has not been populated at that time. Calling dispatch_async causes that block of code to be run asynchronously and allows the viewDidLoad function to finish before the block does. That's why you have nothing in your array at the end of viewDidLoad.
You are trying to print count of jsonArray elements before it was populated. Here is what happens:
You prepare url
You create new thread to fetch some date. Execution of this thread may take some time (depending on connection speed and amount of data).
You are accessing jsonArray while thread is executing and fetchedData: was not called
Also, suggestion:
Don't use dataWithContentsOfURL: methods. Better take a look at some networking frameworks like AFNetworking.
I'm new to ios development.My app gets slower when i'm parsing image using json parser in ios 5.
Please could anybody help to solve this problem.
-(NSDictionary *)Getdata
{
NSString *urlString = [NSString stringWithFormat:#"url link"];
urlString = [urlString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSURL *url = [NSURL URLWithString:urlString];
NSData* data = [NSData dataWithContentsOfURL:url];
NSError* error;
NSDictionary* json;
if (data) {
json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
NSLog(#"json...%#",json);
}
if (error) {
NSLog(#"error is %#", [error localizedDescription]);
// Handle Error and return
// return;
}
return json;
}
Your description of the problem isn't exactly helpful. It's unclear to me if everything in your app is slow, or just certain operations; if you exprience a slow action and then it becomes fast again or if it continues to perform slowly.
Whatever, the general rule is to performan all network communication including the parsing of the answer on a separate thread, i.e. not on the main thread that is responsible for managing the user interface. That way the app remains responsive and appears to be fast.
If you can download the images separately, you can already display the result and put a placeholder where the image will appear. Later, when you have received the image you remove the placeholder and put the image there.
This line is probably the culprit.
NSData* data = [NSData dataWithContentsOfURL:url];
If you're calling this on the main thread (and because you haven't mentioned threads at all I suspect that you are) it will block everything and wait until the server has responded.
This is a spectacularly bad experience for the user :)
You need to do all of this on a background thread and notify the main thread when you're done. There's a couple of ways of doing this (NSOperation etc) but the simplest is just this :
// Instead of calling 'GetData', do this instead
[self performSelectorOnBackgroundThread:#selector(GetData) withObject:nil];
// You can't return anything from this because it's going to be run in the background
-(void)GetData {
...
...
// Instead of 'return json', you need to pass it back to the main thread
[self performSelectorOnMainThread:#selector(GotData:) withObject:json waitUntilDone:NO];
}
// This gets run on the main thread with the JSON that's been got and parsed in the background
- (void)GotData:(NSDictionary *)json {
// I don't know what you were doing with your JSON but you should do it here :)
}