from one screen on click of a button i am navigating on other screen, i am navigating properly but screen is getting hang till the screen to which i am navigating gets web service response
(in viewDidLoad i am calling a web service)
how to fix this
Thanks
Move web service call to viewWillAppear or viewDidAppear, so call will initiate after controller view appears on screen.
Ideally you should perform the web service call in the background i.e. not on the main thread. Use NSOperation and NSOperationQueue or AFNetworking or Grand Central Dispatch. You can then initiate the call in viewDidLoad itself.
Here are a few links that can get you started.
How To Use NSOperations and NSOperationQueues
Networking Made Easy With AFNetworking
iOS Quick Tip: Interacting with Web Services - for using GCD
Hope that helps!
This is happen because of webservice contains heavy data that's whay you need to try this.
Load your images using [NSURLConnection sendAsynchronousRequest:queue:completionHandler: then use NSCache to prevent downloading the same image again and again.
As suggested by many developers go for SDWebimage and it does include the above strategy to download the images files .You can load as many images you want and the same URL won't be downloaded several times as per the author of the code
Example on
[NSURLConnection sendAsynchronousRequest:queue:completionHandler:
NSURL *url = [NSURL URLWithString:#"your_URL"];
NSURLRequest *myUrlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest: myUrlRequest queue: queue completionHandler: ^ (NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil)
//doSomething With The data
else if (error != nil && error.code == ERROR_CODE_TIMEOUT)
//time out error
else if (error != nil)
//download error
}];
Then use NSCache...
Related
I have some data that I am getting from a server and then displaying in my UIViewController class. To do this, I have two classes. The UIViewController and another one named ServerCommunicator. UIViewController is the delegate for ServerCommunicator class. The serverCommunicator looks as follows:
- (void)fetchServerData:(NSString *) serverAddress{
NSURL *url = [[NSURL alloc] initWithString:serverAddress];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
[self.delegate fetchingSongsFailedWithError:error];
} else {
[self.delegate receivedSongsJSON:data];
}
}];
}
The UIViewController allocates the serverCommunicator, sets itself as delegate and then issue the fetch request.
- (void)viewDidLoad {
[super viewDidLoad];
self.songServerCommunicator = [[serverCommunicator alloc] init];
self.songServerCommunicator.delegate = self;
[self.songServerCommunicator fetchServerData:<some_server_ip>];
}
After it does that it implements the required protocol method:
- (void)receivedSongsJSON:(NSData *)data{
NSLog(#"received server response");
/* Parses the data and displays in textfield/imageview */
}
My problem is that when I do display the data received in the delegate method, it doesn't get reflected right away in the UI. It is very weird, sometimes it gets shown 20 seconds laters on its own, other times it takes like a minute. I am not sure whats going on. I know for a fact that the data was fetched right away because the logged message gets printed way before the UIView gets updated.
Thanks for any help on this.
Make sure you are on the main thread when you update the UI
Other people have pointed out the problem, but they did not provide the solution in concrete code. This is the coded solution:
- (void)fetchServerData:(NSString *) serverAddress{
NSURL *url = [[NSURL alloc] initWithString:serverAddress];
[NSURLConnection sendAsynchronousRequest:[[NSURLRequest alloc] initWithURL:url] queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(
dispatch_get_main_queue(),
^void {
if (error) {
[self.delegate fetchingSongsFailedWithError:error];
} else {
[self.delegate receivedSongsJSON:data];
}
}
);
}];
}
You must understand how Grand Central Dispatch works in iOS. GCD is an abstraction layer for multithreading.
The main queue, which your UI runs on, is a serial queue. That means you can't have two blocks of code running at the same time. It would be poor user experience if you were to do a long network query on the main queue, because it would prevent any UI code from running. This would make the app appear like it is frozen to the user.
To solve the freezing UI issue, iOS gives you other queues to do work without blocking up the main queue. iOS provides ways to create your own custom queues or use pre-made global concurrent queues. The queues that NSURLConnection uses is not known to us because we don't have the Cocoa source code. But what we do know is that NSURLConnection is definitely not using the main queue because UI is not frozen while it is grabbing data from a server.
Since NSURLConnection is running on a different thread than what the main queue (UI) runs on, you cannot just update UI at any random time. This is a common problem with multithreaded programs. When multiple threads access the same block of code, instructions may be interleaved and both blocks get unexpected results. One of the unexpected results that you experienced was the 20 second delay of the UI finally being updated. To prevent interleaving, you need to run all the UI code on the same thread. You do this by enqueuing your UI update code to the end of the main queue.
The method receivedSongsJSON() is called from a callback given to [NSURLConnection sendAsynchronousRequest] which I think is being called from a background thread.
Even if the method receivedSongsJSON() is declared in your UIViewController it will be executed in background thread if it is called from one.
As #robdashnash has indicated make sure you call all the UI updating code from main thread. If you are not sure how to do that please check the documentation of Grand Central Dispatch (GCD) here.
I probably ask my question the wrong way and risk being blocked by stackoverflow completely. I have Asperger and no social skills, so I am very sorry for asking my last (?) question (because systems like these are only made for people without handicaps).
I am using GCD to load images and video from Instagram. I do this in an app that is very 'busy' with its user interface, and I want to keep that running smoothly, so I load the Instagram-media in the background.
The code below (which I probably formatted the wrong way so I apologize up front) works fine and does exactly want I want it to do. It loads images in the background (I left video's out to keep things simple) and my main UI is response while the images load. I display them between loads, and there works fine too.
However.
After 10 minutes, sometimes 20 minutes, sometimes 30 minutes and even sometimes after two hours my app gets OSSpinLockLock freezes. If I remove the code below I get no media, but the app never freezes.
I have searched for days on the web about alternative ways to do the job and to find an explanation for the OSSpinLockLock. No luck. Only thing I found was that using GCD could not result in an OSSpinLockLock. I have used all my knowledge of Instruments (which I must admit is more limited than I thought), but I cannot find a fault.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^(void) {
[[InstagramEngine sharedEngine] getMediaAtLocation:location count:kInstagramLocationBufferSize maxId:nil withSuccess:^(NSArray* media, InstagramPaginationInfo* paginationInfo) {
if (media && media.count>0) {
for (InstagramMedia* mediaObject in media) {
NSData* data = [[NSData alloc] initWithContentsOfURL:mediaObject.standardResolutionImageURL];
if (data) {
UIImage* img = [UIImage imageWithData:data];
if (img)
[self.locationBuffer addObject:img];
data = nil;
}
}
}
} failure:^(NSError *error) {
;
}];
});
If you look at this code, do you see anything that might cause that lock? Because I most certainly don't.
self.locationBuffer is declared in the .h as
#property (nonatomic,strong) NSMutableArray* locationBuffer;
and is properly allocated and initialized (otherwise it would be rather clear what the problem was). I have also tried not to put the UIImage but the NSData in the array but that made no difference whatsoever.
On my iPad mini retina for instance the CPU-load goes to 195% and stays around that number for a very long time. Eventually, sometimes after several hours, the app crashes.
Any suggestions would be very welcome.
Edit: As I see now on the ips-file on the iPad itself (which for some mysterious reason I cannot paste into this webpage (is stackoverflow still in an experimental stage?)) I see that the iPad did spent 16.000+ seconds on NSURLConnection...
my tought is that some timeout or failure blocks your gcd queues for too much time. try rewriting that code with NSOperationQueue, that way you can stop the queue on errors or view/controller going away.
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/NSOperationQueue_class/index.html
Update 1:
Here is my trial at your code, i added queue and logs, check them and also check the timeout value. This way if a request does not finish (most likely) you can trace it. All requests are serial so if one of them stops you should immediately notice it.
You can create more than one queue and access them sequentially (round robin) to have more requests simultaneously. I would not go with more than 4 queues which is also the default for most desktop internet browsers.
// keep the same queue for all request (class member?), don't put it in the same block
NSOperationQueue* queue= [[NSOperationQueue alloc] init];
// keep this in another code block from queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^(void) {
[[InstagramEngine sharedEngine] getMediaAtLocation:location count:kInstagramLocationBufferSize maxId:nil withSuccess:^(NSArray* media, InstagramPaginationInfo* paginationInfo) {
if (media && media.count>0) {
for (InstagramMedia* mediaObject in media) {
NSURL* url= mediaObject.standardResolutionImageURL;
NSLog(#"Start loading from %#", url);
NSURLRequest* req= [NSURLRequest requestWithURL:mediaObject.standardResolutionImageURL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30]; // 30 seconds timeout
[NSURLConnection sendAsynchronousRequest:req queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(#"Stop loading from %# %# %#", url, response, connectionError);
if (data != nil && connectionError == nil) {
UIImage* img = [UIImage imageWithData:data];
if (img) {
// if you are triggering some ui update run on main thread
// [self.locationBuffer addObject:img];
[self.locationBuffer performSelectorOnMainThread:#selector(addObject:)
withObject:img
waitUntilDone:NO];
}
}
}];
}
}
} failure:^(NSError *error) {
NSLog(#"Error getting media list: %#", error);
}];
});
OSSpinLocks has bug on iOS8.3+: report
,which leads to such freezes.
I think you should replace OSSpinLocks with something else to fix this in your app (maybe create your own realization of spin lock - search SO for OSSpinLock).
Intro:
I have basically learned Obj-C and Cocoa Touch from Stackoverflow over the last few weeks, so bravo community, bravo.
Alas I have come to a question I cannot find an answer to/don't know how to Google.
I am making an app that 100% interfaces with an API service. Here's the process:
App launch -> Retrieve token
Subsequent requests -> HTTP Header token
My first view controller is a loading icon while a synchronous request takes place.
I would like to make Async requests in the future as currently my UI gets blocked while these requests take place.
My PHP background has caused me to set up my files like this, so let me know if this is incorrect.
APIConnect.h/.m -> Holds all my API methods
-(NSDictionary *)APIcall:(NSString *)api postData:(NSDictionary *)postData
TableViewController.h/.m -> Table based off data loaded from the method above.
When selecting a row, it moves forward in the UI and retrieves the details of that item (another API call).
When moving forward, it's apparent the main thread is blocked.
I therefore have tried to set up an async method
-(NSDictionary *)asyncAPIcall:(NSString *)api postData:(NSDictionary *)postData
APIConnect.m (abbreviated):
-(NSDictionary *)asyncAPIcall:(NSString *)api postData:(NSDictionary *)postData
{
[NSURLConnection sendAsynchronousRequest:request
queue:queue
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error){
if ([data length] > 0 && error == nil)
[self returnedData:data];
else
[self downloadError:error];
}];
}
-(void)returnedData:(NSData*)data
{
NSMutableDictionary *returnData = [[NSMutableDictionary alloc] init];
[returnData setDictionary:[NSJSONSerialization JSONObjectWithData:data
options:0
error:nil]];
NSLog(#"Returned Data %#", returnData);
[self setReturnURLData:returnData];
}
I really don't know how to ask my question, so maybe you can help me with that... However in theory I would like to:
Make an async request within APIConnect.m
Once async is done, it returns data to TableViewController.m
Once TableViewController receives data it will run it's processing and run [self.tableView reloadData];
All meanwhile, an activity indicator or something is visible to show that it's working on getting something
Any guidance on what to Google would be super helpful.
I have some difficulties to set up the correct configuration relative to sendAsynchronousRequest:queue:completionHandler: method (NSURLConnection class).
My scenario is the following:
I set up a singleton class that manages different NSURLConnections. This singleton istance has a NSOperation Queue (called downloadQueue) that makes a request to a web server and retrieves a string path (1).
Once done, the path is used to download a file within a web server (2). Finally, when the file has been correctly downloaded, I need to update the UI (3).
I figured out only the first request: the one through which I'm able to download the path. Could you suggest me a way to perform the other two steps?
Few questions here:
the download queue (downloadQueue) is not the main one, is it possible to open a new NSURLConnection in that queue? In other words, is it correct? (See comments in code snippets)
if the previous question is correct, how can I grab the main queue and update the UI?
Here the code snippet I use to perform the first step where downloadQueue is an instance variable that can be obtain through accessor mehods (#property/#synthesized);
// initializing the queue...
downloadQueue = [[NSOperation alloc] init];
// other code here...
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[self downloadQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if([data length] > 0 && error == nil) {
// here the path (1)
// how to perform a second connection?
// what type of queue do I have to use?
}
}];
You're on the right track for performing your first download.
In the completion handler block after the first download, you're computing the URL that you'll need for a second download, right? Then you can perform that second download the same way: call +[NSURLConnection sendAsynchronousRequest:...] again with the new URL and the same queue. You can do this within the completion block for the first download.
To update the UI after the second download is done, switch to the main queue within the completion block for that download. You can do this with dispatch_async() or dispatch_sync() (in this case it doesn't matter which because you don't have further work to do on the download queue) and dispatch_get_main_queue(), or with -[NSOperationQueue addOperationWithBlock:] and +[NSOperationQueue mainQueue].
Your code should look something like this:
// init download queue
downloadQueue = [[NSOperationQueue alloc] init];
// (1) first download to determine URL for second
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[self downloadQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if([data length] > 0 && error == nil) {
// set newURLRequest to something you get from the data, then...
// (2) second download
[NSURLConnection sendAsynchronousRequest:newURLRequest queue:[self downloadQueue] completionHandler:^(NSURLResponse *newResponse, NSData *newData, NSError *newError) {
if([newData length] > 0 && newError == nil) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// (3) update UI
}];
}
}];
}
}];
For updating the ui, as far as I know, you have to do that on the main thread. The ui could be updated from other threads but those updates are not fully reliable. In an app that I put together that made request to a web service, I make use of dispatch_async() to get access to the main queue and then I update, in my case a table view, from that call.
dispatch_async(dispatch_get_main_queue(), ^{
//block to be run on the main thread
[self.tableView reloadData];
});
I hope this helps.
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.