Application freeze while executing fetch request in iOS - ios

I am requesting an API that takes some time to give response, so during that time no other operation can be performed. e.g. back button or Tabs are not pressed. I am Using the following code:
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
NSURL * url = [NSURL URLWithString:urlString];
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil)
{
}else{
}
}];
[dataTask resume];
Can anyone Suggest why my app is freezing during this operation. Thanks in advance.

Because you are performing operation in main thread, you need to do this task in background thread.
For this you can use NSOperationQueue and add a operation of you api calling.
see belo links
NSOperation and NSOperationQueue working thread vs main thread
How To Use NSOperations and NSOperationQueues
Working with the NSOperationQueue Class
or you can also use DispatchQueue
see : Multithreading and Grand Central Dispatch on iOS for Beginners Tutorial
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// simply call your function here
});

you are doing operation on main thread swhich interrupt app execution. you should do this operation in background through GCD by creating async request to download data in background it will not interrupt your app execution.
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
NSURL * url = [NSURL URLWithString:urlString];
NSData *response = [[NSData alloc] initWithContentsOfURL:url];
// above code will download data in background
dispatch_async(dispatch_get_main_queue(), ^{
// here you can access main thread of application and do something here
});
});

Related

iOS 14 crash zombie when use dispatch_semaphore

I handle some old code, it runs well, but now crash only on ios 14
here is the demo
static NSData *DownloadWithRange(NSURL *URL, NSError *__autoreleasing *error) {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:URL];
request.timeoutInterval = 10.0;
__block NSData *data = nil;
__block dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSURLSessionConfiguration *config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
NSURLSession *URLSession = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [URLSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable taskData, NSURLResponse * _Nullable response, NSError * _Nullable taskError) {
data = taskData;
if (error)
*error = taskError;
dispatch_semaphore_signal(sema);
}];
[task resume];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return data;
}
- (IBAction)crashButton:(id)sender {
NSURL *url = [NSURL URLWithString:#"http://error"];
NSError * error = nil;
NSData *compressedData = DownloadWithRange(url, &error);
NSLog(#"error is %#",error);
}
before DownloadWithRange returned, the taskError memory(NSURLError) has released
on ios 13, it don't crash
it's really weird
The zombie diagnostics are letting you know that the autorelease object is getting deallocated by the time the data is returned. You should not be instantiating an autorelease object in one thread and trying to have a pool on a separate thread manage that. As the docs say:
Autorelease pools are tied to the current thread and scope by their nature.
While the problem might be manifesting itself differently in iOS 14, I do not believe that this pattern was ever acceptable/prudent.
If you're going to use this pattern (which I wouldn't advise; see below), you can solve this problem by copying the error object on the calling thread before returning:
static NSData *DownloadWithRange(NSURL *URL, NSError * __autoreleasing *error) {
...
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
if (error) {
*error = [*error copy];
}
return data;
}
FWIW, this technique of using semaphore to make asynchronous method behave synchronously is generally considered an anti-pattern. And you definitely should never use this pattern from the main thread.
I would suggest adopting asynchronous patterns:
- (NSURLSessionTask *)dataTaskWithURL:(NSURL *)url completion:(void (^ _Nonnull)(NSData * _Nullable data, NSError * _Nullable error))completion {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.timeoutInterval = 10.0;
NSURLSessionConfiguration *config = NSURLSessionConfiguration.ephemeralSessionConfiguration;
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
completion(data, error);
});
}];
[task resume];
[session finishTasksAndInvalidate];
return task;
}
And
[self dataTaskWithURL:url completion:^(NSData * _Nullable data, NSError * _Nullable error) {
// use `data` and `error` here
}];
// but not here
Note, in addition to adopting asynchronous completion block pattern, a few other observations:
If you’re going to create a new NSURLSession for each request, make sure to invalidate it or else you will leak memory.
I’m returning the NSURLSessionTask, which some callers may want in case they might want to cancel the request (e.g. if the view in question is dismissed or a new request must be generated). But as shown above, you don’t need to use this NSURLSessionTask reference if you don’t want.
I'm dispatching the completion handler back to the main queue. That is not strictly necessary, but it is often a useful convenience.

NSURLSession doesn't return data

I try to download a zip-archive using NSURLSessionDataTask.
I am aware that there is a NSURLSessionDownloadTask, but the point is I want a didReceiveData callback (to show the progress).
The code is:
NSURLRequest *request = [NSURLRequest requestWithURL:#"..."
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.underlyingQueue = dispatch_get_main_queue();
NSURLSession *session = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:myQueue];
NSURLSessionDataTask* task = [session dataTaskWithRequest:request
completionHandler:^( NSData *data, NSURLResponse *response, NSError *error){ ... }
[task resume];
My class conforms to NSURLSessionDataDelegate.
When I call the method, after several seconds debugger goes to completionHandler with nil data and nil error.
What am I doing wrong?
I also tried:
calling without completionHandler, then debugger goes to didReceiveResponse callback with 200 response and that's all.
using [NSOperationQueue new] for the queue
using [NSURLSession sharedSession] - didn't get any response
using [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: #"..."] - falls saying that I can't use a completionHandler, but without it - also no response.
So I have found the answer and it's not quite obvious from documentation:
I had several callbacks, and among them didReceiveResponse.
Turns out I have to call completion handler in order for the future callbacks to work, i.e:
completionHandler(NSURLSessionResponseAllow);
And one more thing: didCompleteWithError is actually the delegate that tells about successful finish, too, although the name implies that this is the error handler.
What it means: when a download is successfully finished, this function is called with error = nil.
Hope this will be useful for somebody someday.

Today Extension for iOS 8 issue when widgetPerformUpdateWithCompletionHandler includes async body

I've working with the new Today Extension available on iOS 8. Debugging seems to be very difficult on the device with inconsistent results so I've been using the simulator most of the time.
The extension I'm building is a very simple one that just display a different image on a daily basis, the flow is actually pretty simple:
iOS calls widgetPerformUpdateWithCompletionHandler
I download the image asynchronously
If the image was downloaded successfully I set the appropriate outlet on the storyboard and call the completion block with the constant: NCUpdateResultNewData
If an error occurred I call the completion block with the constant: NCUpdateResultFailed
According to Apple's reference documentation every time we call the completion block with the constant NCUpdateResultNewData the widget's snapshot should be updated to the current view, however, this doesn't work all the time, sometimes iOS seems to be using an older snapshot.
The code is straightforward, here's the widgetPerformUpdateWithCompletionHandler code:
-(void)widgetPerformUpdateWithCompletionHandler:(void (^)(NCUpdateResult))completionHandler {
__weak TodayViewController *weakSelf = self;
NSURL *URL = [NSURL URLWithString:#"http://www.muratekim.com/wp-content/uploads/apple-logo-small.png"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:data];
weakSelf.imageView.image = image;
weakSelf.img = image;
completionHandler(NCUpdateResultNewData);
}
else {
completionHandler(NCUpdateResultFailed);
}
}];
[task resume];
}
Thanks in advance!
Ze

Non-unique NSURLSessionDataTask taskIdentifiers

I have an iOS application which is using an NSOperationQueue, NSOperations and AFNetworking 2.1.0 to fire off requests to a server. The -[NSOperation main] method looks something like:
- (void)main {
AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager sharedSessionManager];
[sessionManager GET:#"url"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(#"Success");
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(#"Failure");
}
];
}
I have noticed that, from time to time, that the callbacks for a particular operation never get executed, when multiple operations are created and added to the NSOperationQueue in quick succession. I dove into AFNetworking to try to figure out why. I ended up in -[AFURLSessionManager dataTaskWithRequest:completionHandler], which looks like:
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
AFURLSessionManagerTaskDelegate *delegate = [AFURLSessionManagerTaskDelegate delegateForManager:self completionHandler:completionHandler];
[self setDelegate:delegate forTask:dataTask];
return dataTask;
}
I added a logging statement right after dataTask is created:
NSLog(#"Task with id %# created for %# on queue %#", #(dataTask.taskIdentifier), request.URL.path, dispatch_get_current_queue());
The log reveals the problem:
2014-02-26 14:11:25.071 App[50094:6a2f] Task with id 15 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:25.071 App[50094:460f] Task with id 16 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:26.274 App[50094:6a2f] Task with id 18 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:26.274 App[50094:6c17] Task with id 17 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:27.546 App[50094:6307] Task with id 20 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:27.546 App[50094:6b17] Task with id 19 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:28.705 App[50094:6b17] Task with id 21 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:28.705 App[50094:6307] Task with id 21 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:32.091 App[50094:6307] Task with id 22 created for /url2 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
2014-02-26 14:11:32.091 App[50094:6b17] Task with id 23 created for /url1 on queue <OS_dispatch_queue: NSOperationQueue 0xc4b8560[0xc4b8ac0]>
Notice the fourth set in the log has the same taskIdentifier which is what AFNetworking uses to associate tasks with their callbacks, via delegate.
If I force the NSOperations to run on the main queue, then I am unable to recreate the issue - the taskIdentifier is always unique.
Has anyone seen anything like this before? Do I need to ensure that -[NSURLSession dataTaskWithRequest:] runs only on the main thread in order to not get taskIdentifier collisions?
Don't know if this is still relevant to you or not, but this exact thing had me banging my head around all night. Turns out you are partially answering the problem in your question.
As it would turn out, if
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
is run asynchronously, then there is the small chance that the instance of NSURLSession will assign the same taskIdentifier to different tasks.
Without forking AFNetworking and synchronizing on all of the dataTaskWithRequest: methods then there were two ways that I could go about fixing this that stood out.
If you don't need the NSURLSessionTask returning from this method then the best way would be to make your own dispatch queue and any requests you want to make with your session manager you just send it to that queue asynchronously like so:
static dispatch_queue_t my_queue;
my_queue = dispatch_queue_create("MyQueueName", DISPATCH_QUEUE_CONCURRENT);
// ... Later, that very same day
dispatch_async(my_queue, ^{
// [sessionManager GET: ...
});
The only problem with this method for your issue however was that it all seemed to be executed on the same operation queue, so maybe this way wouldn't work in your case. The other way (which is how I did it) is actually waay simpler. Just synchronize on the AFURLSessionManager so the invocation of dataTaskWithRequest: can only ever happen synchronously like so:
#synchronized(sessionManager) {
// [sessionManager GET: ...
}
Or, yes, you could just do the task creations on the main thread. But for some projects its not always as simple as that.
I would enqueue a NSURLSessionDataTask to your AFURLSessionManager instead of trying to directly make GET requests. The session manager is intended to abstract the functionality of NSURLSession.
Example:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:#"http://example.com/testapi.php"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(#"error!");
} else {
NSLog(#"task successful!");
}
}];
[dataTask resume];

multiple download NSURLSession

I have a program that download a video from a url using NSURLSession, but i'm not able to do multiple download at the same time.
How can i do it?
How can i manage multiple simultaneous download?
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *getVideo = [session downloadTaskWithURL:fileURL
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
// 2
receivedData = [NSData dataWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
NSLog(#"%s receiveData:%d",__FUNCTION__,[receivedData length]);
});
}];
[getVideo resume];
From the code you have provided above you are not using any of the properties of NSURLSessionConfiguration class that would enable better download performance.
First of all I would look suggest using your own delegate queue. If you do not provide a queue then the session creates a serial operation queue for all delegate and completion handler calls see the "Creating a Session" section of the NSURLSession Class Reference document for more detail. You can look at the following properties of NSOperationQueue to help improve performance;
qualityOfService
maxConcurrentOperationCount
Next I would look at NSURLSessionConfiguration properties that may help.
HTTPMaximumConnectionsPerHost
HTTPShouldUsePipelining
Finally you should review the section "Life Cycle of a URL Session with Custom Delegates". You should confirm whether your using the delegate methods of NSURLSessionTaskDelegate and NSURLSessionDownloadTaskDelegate or just the completion handler.
You need to put more time into configuring NSURLSession to support the work you want to do.

Resources