I am needing to perform a very simple background check for my iOS app. It needs to just make one call to my web server and check the number it retrieves against something in my app. Is it possible to do that kind of background check? If so what can I do to put it together?
EDIT
To clarify what I mean by background: I am meaning background on the phone. When the app is not present. Is it possible to do this request in the background? Obviously with the app not being completely closed out from multitasking.
This sounds like the perfect sort of thing for NSOperationQueue.
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
You can write an operation and then put it on the queue when you need it.
Alternatively, and more simply, you can just do a really simple asynchronous call.
+ (NSArray *) myGetRequest: (NSURL *) url{
NSArray *json = [[NSArray alloc] init];
NSData* data = [NSData dataWithContentsOfURL:
url];
NSError *error;
if (data)
json = [[NSArray alloc] initWithArray:[NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:&error]];
if (error)
NSLog(#"%#", error)
return json;
}
and then put it in a simple dispatch block...
dispatch_queue_t downloadQueueA = dispatch_queue_create("updater", NULL);
dispatch_async(downloadQueueA, ^{
// stuff done here will happen in the background
NSArray * arrayOfData = [self myGetRequest: myURL];
// could be array... dictionary... whatever, you control this by returning the type of data model you want from the server formatted as JSON data
NSString * stringValue = arrayOfData[index];
dispatch_async(dispatch_get_main_queue(), ^{
// perform checking here and do whatever updating you need to do based on the data
});
});
There are many way to check your server and retrieve data.
Here my suggestion:
Create the file containing your data on the server (e.g. Data.txt)
Use NSURLRequest to create a request to Data.txt
Use connectionDidFinishLoading to get data from Data.txt
Put data from Data.txt in a NSArray
Work/compare the array and do your logic
If your server is fast and you have to get just one number, you can do it in the main tread, otherwise use:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// your request here
});
to work in a different tread as requested.
And remember to check if internet connection and your server are available with something like Reachability and manage connection error with NSURLRequest delegate
You should be able to do that using Grand Central Dispatch: https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
Take a look at this tutorial Multithreading and Grand Central Dispatch on iOS.
http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial
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];
I am using objective-C to write an app which needs to dispatch 100 web request and the response will be handled in the call back. My question is, how can I execute web req0, wait for call back, then execute web req1 and so on?
Thanks for any tips and help.
NSURL *imageURL = [[contact photoLink] URL];
GDataServiceGoogleContact *service = [self contactService];
// requestForURL:ETag:httpMethod: sets the user agent header of the
// request and, when using ClientLogin, adds the authorization header
NSMutableURLRequest *request = [service requestForURL:imageURL
ETag: nil
httpMethod:nil];
[request setValue:#"image/*" forHTTPHeaderField:#"Accept"];
GTMHTTPFetcher *fetcher = [GTMHTTPFetcher fetcherWithRequest:request];
fetcher.retryEnabled = YES;
fetcher.maxRetryInterval = 0.3;
fetcher.minRetryInterval = 0.3;
[fetcher setAuthorizer:[service authorizer]];
[fetcher beginFetchWithDelegate:self
didFinishSelector:#selector(imageFetcher:finishedWithData:error:)];
}
- (void)imageFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *)error {
if (error == nil) {
// got the data; display it in the image view. Because this is sample
// code, we won't be rigorous about verifying that the selected contact hasn't
// changed between when the fetch began and now.
// NSImage *image = [[[NSImage alloc] initWithData:data] autorelease];
// [mContactImageView setImage:image];
NSLog(#"successfully fetched the data");
} else {
NSLog(#"imageFetcher:%# failedWithError:%#", fetcher, error);
}
}
You can't simply call this code in a loop as GTMHTTPFetcher works asynchronously so the loop, as you see, will iterate and start all instances without any delay.
A simple option is to put all of the contacts into a mutable array, take the first contact from the array (remove it from the array) and start the first fetcher. Then, in the finishedWithData callback, check if the array contains anything, if it does remove the first item and start a fetch with it. In this way the fetches will run serially one after the other.
A better but more complex solution would be to create an asynchronous NSOperation (there are various guides on the web) which starts a fetch and waits for the callback before completing. The benefit of this approach is that you can create all of your operations and add them to an operation queue, then you can set the max concurrent count and run the queue - so you can run multiple fetch instances at the same time. You can also suspend the queue or cancel the operations if you need to.
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 :)
}
I have a table view that works fluidly until I add a URL request to the code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
//Get Total Comments
NSString *strURL = [NSString stringWithFormat:#"http://XX.XX.XX.XX/php/commentsTotal.php?CID=%#", [dict objectForKey:#"id"]];
NSData *dataURL = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
// to receive the returend value
NSString *strResultCI = [[NSString alloc] initWithData:dataURL encoding:NSUTF8StringEncoding];
cell.commentCount.text = strResultCI;
return cell;
}
The issue is that as you scroll through the table cells, the phone has to communicate back to my server, wait for the response and then display it to the cell.
Needless to say it has crippled my table performance. My question is: Does anyone have a good example or tutorial on how to simply add a JSON data request to a background thread? I am using SDWebImage to asynchronously handle the images, but don't know where to begin with the data portion.
I think what you need to do is:
make a simple cache like an array of dictionaries, where key is url and value is data.
when you show a new cell check the cache at first, if nothing is in there - send asynchronous request to the server (also it is good to know if we are waiting for the response)
when you receive a response from the server fill the cache and check the tableView visible cells, if you received a data for a visible cell do it using tableView updates (not reload data, because it would be slow)
as for me, i am using AFNetworking library for API calls (also ASIHTTPRequest was good)
by the way, i think you should cancel requests when user scrolls fast, that could be done through NSOperationQueue. You probably don't want all of those requests be running at the same time, it is better to have only those active, which data you need most and cancel others
If this is the only point at which you will do server/client communication, you simply need to do an asynchronous NSURLConnection.
Else if you are doing a lot of client/server communication the best approach would be AFNetworking or any other http client library.
when ever you need to retrieve JSON data back from a webserver and need to do it in a background thread try doing:
dispatch_queue_t webCall = dispatch_queue_create("web call", NULL);
dispatch_async(webCall, ^{
NSString *strURL = [NSString stringWithFormat:#"http://XX.XX.XX.XX/php/commentsTotal.php?CID=%#", [dict objectForKey:#"id"]];
NSData *dataURL = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]]];
});
dispatch_async(dispatch_get_main_queue(), ^{
NSString *strResultCI = [[NSString alloc] initWithData:dataURL encoding:NSUTF8StringEncoding]; cell.commentCount.text = strResultCI;
});
use NSJSONSereliazation class in foundation to parse the json data. it returns either dictionary or a array depending on the data. dispatch_async(webCall, ^...); creates a background thread for you and dispatch_async(dispatch_get_main_queue(), ^... get the main thread back, this is needed for when you need to do anything UI related like change the cells texts.
Also note, try to have your table view cells data ready before hand and not in -tableView: cellForIndexPath.
I've started to use blocks and queues heavily and they have been great. I use much less code and it is much easier to build and maintain. But I wonder about performance. In one case I am displaying a screen full of thumbnail images from a Flickr photo set. The code iterates over all items and starts a unique download queue to download each photo concurrently. It's working just fine, but I wonder if I should instead create a single static queue for downloading photos and then dispatch these download blocks to the same queue so that it can manage the blocks efficiently.
I've uploaded an example here.
http://www.smallsharptools.com/Downloads/iOS/UIImage+DownloadImage.zip
The implementation contents are also below. I appreciate any insight into better performance. (Later I'd like to handle caching for images by placing the file in the tmp folder so they are automatically cleared out periodically.)
How do you manage concurrent tasks with blocks? Do you create a static queue and dispatch blocks to the shared queue? Or does the implementation below implicitly manage all of my tasks efficiently already?
#import "UIImage+DownloadImage.h"
#implementation UIImage (DownloadImage)
+ (void)downloadImageWithURL:(NSURL *)imageURL andBlock:(void (^)(UIImage *image, NSError *error))returnImage {
dispatch_queue_t callerQueue = dispatch_get_current_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Image Download Queue", NULL);
dispatch_async(downloadQueue, ^{
UIImage *image = nil;
NSError *error = nil;
// use the default cache policy to do the memory/disk caching
NSMutableURLRequest *request = [NSMutableURLRequest
requestWithURL:imageURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:15];
NSHTTPURLResponse *response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
// 200 indicates HTTP success
if (response.statusCode != 200) {
data = nil;
// set the error to indicate the request failed
NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: [NSString stringWithFormat:#"Request failed with HTTP status code of %i", response.statusCode], NSLocalizedDescriptionKey, nil];
error = [NSError errorWithDomain:#"UIImage+DownloadImage" code:response.statusCode userInfo:userInfo];
}
else if (!error && data) {
image = [UIImage imageWithData:data];
}
// image will be nil if the request failed
dispatch_async(callerQueue, ^{
returnImage(image, error);
});
});
dispatch_release(downloadQueue);
}
#end
It does seem inefficient to create a 1-element queue each time, though I would be surprised if this would show up as a hotspot during profiling.
If you search on Apple's iOS forums, you should be able to find Quinn's discussion of using NSURLConnection "raw" rather than via threads.
You're doing synchronous network activity on queues. This seems like a rather poor idea, since you're blocking threads and forcing GCD to spin up new threads to service other blocks. If you're downloading 20 images simultaneously, then you will have 20 blocked threads in your app and another handful to actually do work. Instead you should be doing asynchronous network activity on a single worker thread. There's even a piece of Apple sample code that does this, though I cannot for the life of me remember what it's called.