The task can be cancelled if while using NSOperation whereas if I use GCD, then once I assign the task to queue then we are not able to cancel it, therefore I wonder how could I convert my following implementation in GCD to NSOperation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
[self addAllImages];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self pageControlSetUp];
self.fullScreenImageView.hidden = YES;
});
})
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
[self addAllImages];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
[self pageControlSetUp];
self.fullScreenImageView.hidden = YES;
});
}];
[queue addOperation:operation];
//cancel operation
[operation cancel];
//or to cancell all operations
[queue cancelAllOperations];
Related
How to cancel all operations in NSOperationQueue? I used cancelAllOperations method, but it didn't work, the NSOperationQueue is still calling server to upload photo.
I put every single connection on NSOperationQueue with loop.
- (void)sendingImage:(NSArray *)imgArray compression:(CGFloat)compression
{
hud = [MBProgressHUD showHUDAddedTo: self.view animated: YES];
hud.label.text = [NSString stringWithFormat: #"Waiting for Loading"];
[hud.button setTitle: #"Cancel" forState: UIControlStateNormal];
[hud.button addTarget: self action: #selector(cancelWork:) forControlEvents: UIControlEventTouchUpInside];
__block int photoFinished = 0;
self.queue = [[NSOperationQueue alloc] init];
self.queue.maxConcurrentOperationCount = 5;
[self.queue addObserver: self forKeyPath: #"operations" options: 0 context: NULL];
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOperation = operation;
__block NSString *response = #"";
for (int i = 0; i < imgArray.count; i++) {
operation = [NSBlockOperation blockOperationWithBlock:^{
[self uploadingPhoto];
}];
[operation setCompletionBlock:^{
NSLog(#"Operation 1-%d Completed", i);
photoFinished++;
dispatch_async(dispatch_get_main_queue(), ^{
hud.label.text = [NSString stringWithFormat: #"%d photo complete uploading", photoFinished];
});
}];
[self.queue addOperation: operation];
}
}
I want to press cancel button on MBProgressHUD to first canceled all the NSURLSessionDataTask and then cancel all operations, but didn't work.
- (void)cancelWork:(id)sender {
NSLog(#"cancelWork");
NSLog(#"self.queue.operationCount: %lu", (unsigned long)self.queue.operationCount);
[session getTasksWithCompletionHandler:^(NSArray<NSURLSessionDataTask *> * _Nonnull dataTasks, NSArray<NSURLSessionUploadTask *> * _Nonnull uploadTasks, NSArray<NSURLSessionDownloadTask *> * _Nonnull downloadTasks) {
if (!dataTasks || !dataTasks.count) {
return;
}
for (NSURLSessionDataTask *task in dataTasks) {
[task cancel];
if ([self.queue operationCount] > 0) {
[self.queue cancelAllOperations];
}
}
}];
}
I used semaphore to let NSURLSession become Synchronous connection.
- (void)uploadingPhoto {
request setting above
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
config.timeoutIntervalForRequest = 1200;
session = [NSURLSession sessionWithConfiguration: config];
dataTask = [session dataTaskWithRequest: request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error == nil) {
str = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"str: %#", str);
}
dispatch_semaphore_signal(semaphore);
}];
NSLog(#"task resume");
[dataTask resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return str;
}
Any comments or solutions will be greatly appreciated.
An NSOperation does not by default have support for cancellation. See the class documentation. One extract is:
Canceling an operation does not immediately force it to stop what it is doing. Although respecting the value in the cancelled property is expected of all operations, your code must explicitly check the value in this property and abort as needed.
It also seems hard to implement cancellation using NSBlockOperation.
my code is loading 7 pics from url and adding their data to an array. in the end of the process I do get an 8 objects array, but I'm trying to show a progress bar until the process of loading all the photos finished.
I do not have an idea how to do that...
here is the code
-(void)SetUpDrinks
{
loadingView.hidden=NO;
[loadingView_activity startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[UIApplication sharedApplication].networkActivityIndicatorVisible=YES;
imgsDATA = [[NSMutableArray alloc] init];
for (int i=0; i<8; i++) {
imageDownloadNum++;
absPath = [NSString stringWithFormat:#"http://domain/app/menu/drinks/%i.png",imageDownloadNum];
trimmedAbsPath = [absPath stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSURL *imgURL = [NSURL URLWithString:trimmedAbsPath];
NSLog(#"%#",imgURL);
imgDATA = [[NSData alloc] initWithContentsOfURL:imgURL];
[imgsDATA addObject:imgDATA];
}
dispatch_async(dispatch_get_main_queue(), ^{
loadingView.hidden=YES;
[loadingView_activity stopAnimating];
[UIApplication sharedApplication].networkActivityIndicatorVisible=NO;
[self RefreshImg];
});
});
}
You could make an NSOperation subclass to download your image and then use a dispatch_group. Dispatch groups are a way to block a thread until one or more tasks finish executing - in this scenario we are waiting for all of your downloads to finish.
You can now use the progressBlock to update the UI and let the user know how many of the images have finished downloading.
If you want progress for an individual download then take a look at the NSURLConnection reference. In particular - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- (void)enqueueGroupOfOperations:(NSArray *)operations
progress:(void (^)(NSUInteger completedCount, NSUInteger totalOperations))progressBlock
completion:(void (^)(NSArray *operations))completionBlock;
{
NSParameterAssert(operations);
NSParameterAssert(progress);
NSParameterAssert(completion);
__block dispatch_group_t group = dispatch_group_create();
NSBlockOperation *dependentOperation = [NSBlockOperation blockOperationWithBlock:^{
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
completion(operations);
});
dispatch_release(group);
}];
for (NSOperation *operation in operations) {
operation.completionBlock = ^{
dispatch_group_async(group, dispatch_get_main_queue(), ^{
NSUInteger count = [[operations indexesOfObjectsPassingTest:^BOOL(NSOperation *operation, NSUInteger idx, BOOL *stop) {
return [operation isFinished];
}] count];
progress(count, [operations count]);
dispatch_group_leave(group);
});
};
dispatch_group_enter(group);
[dependentOperation addDependency:operation];
}
[self.operationQueue addOperations:operations waitUntilFinished:NO];
[self.operationQueue addOperation:dependentOperation];
}
If this is too much for you then you can go over to AFNetworking where this is all done for you, https://github.com/AFNetworking/AFNetworking. But its always nice to know how some of this stuff works.
I am running around the issue, that MBProgressHUD should be updating its View with a Checkmark/X for succeeded/failed requests. Somehow this doesnt really work as intended and the update only works after all the code has executed.
initializing the HUD
...
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = #"Verifying Credit Card";
_HUD = hud;
CWAPIClient *client = [CWAPIClient sharedClient];
client.delegate = self;
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.clubw.billing", 0);
dispatch_async(backgroundQueue, ^{
[client save:billingProfile with:[Address defaultBillingAddress]];
});
....
Callback to process information:
self POST:[NSString stringWithFormat:#"user/%#/billingprofile", [[Profile defaultProfile] userId]] parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate CWAPIClient:self doneVerifyingCreditCard:responseObject];
});
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#", error);
}];
callback when action is complete
BOOL success = [[jsonResponse objectForKey:#"success"] boolValue];
if (success)
{
NSLog(#"Thread: %#", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *checkmarkImage = [UIImage imageNamed:#"checkmark.png"];
UIImageView *checkmarkView = [[UIImageView alloc] initWithImage:checkmarkImage];
_HUD.customView = checkmarkView;
_HUD.labelText = #"Credit Card Verified!";
_HUD.mode = MBProgressHUDModeCustomView;
NSLog(#"Thread: %#", [NSThread currentThread]);
sleep(5);
[_HUD hide:YES];
});
After the 5 seconds, it simply closed the HUD - and the updated one flashes for a second.
It seems, like this is a threading issue - but I cant seem to figure out where it is throwing up.
In the portion of the code where you make your callback, be sure to perform the callback on the main queue as well.
I am trying to implement asynchronous url requests in a particular function, I want all these requests to complete and then do a particular action but the action precedes the requests i.e, it is getting called before the requests complete.
dispatch_queue_t fetchQ = dispatch_queue_create("Featured Doc Downloader", NULL);
dispatch_async(fetchQ, ^{
[self myAsyncMultipleURLRequestFunction];
dispatch_sync(dispatch_get_main_queue(), ^{
[self updateUIFunction];
});
});
-(void)myAsyncMultipleURLRequestFunction
{
for (int i=0; i<count; i++)
{
NSURLConnection *loginConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
}
}
now updateUIFunction gets called before myAsyncMultipleURLRequestFunction completes all requests. Also tried this with NSOperaitonQueue but could not do what I really want.
[_operationQ addOperationWithBlock: ^ {
for (int i=0; i<count; i++)
{
NSURLConnection *loginConnection = [[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
}
}
[[NSOperationQueue mainQueue] addOperationWithBlock: ^ {
// updating UI
[self updateUIFunction];
}];
}];
I know this is simple but I am running outta time, any help is appreciated.
#tkanzakic is on the right path. The correct construct to use is the dispatch_group_t. But the implementation could be improved. By using a semaphore you can launch all your downloads asynchronously and still make sure that you don't have too many running concurrently. Here is a code sample that illustrates how you should use dispatch_group_t as well as make all your downloads parallel:
dispatch_queue_t fetchQ = dispatch_queue_create("Featured Doc Downloader", NULL);
dispatch_group_t fetchGroup = dispatch_group_create();
// This will allow up to 8 parallel downloads.
dispatch_semaphore_t downloadSema = dispatch_semaphore_create(8);
// We start ALL our downloads in parallel throttled by the above semaphore.
for (int i=0; i<count; i++) {
dispatch_group_async(fetchGroup, fetchQ, ^(void) {
dispatch_semaphore_wait(downloadSema, DISPATCH_TIME_FOREVER);
NSURLConnection *loginConnection = [[NSURLConnection alloc] initWithRequest:requestArray[i] delegate:self];
dispatch_semaphore_signal(downloadSema);
});
}
// Now we wait until ALL our dispatch_group_async are finished.
dispatch_group_wait(fetchGroup, DISPATCH_TIME_FOREVER);
// Update your UI
dispatch_sync(dispatch_get_main_queue(), ^{
[self updateUIFunction];
});
// Release resources
dispatch_release(fetchGroup);
dispatch_release(downloadSema);
dispatch_release(fetchQ);
You can create a dispatch_group_t and then use dispatch_group_notify to execute the updateUIFunction when the previous block of the group finish running, for example:
dispatch_queue_t fetchQ = dispatch_queue_create("Featured Doc Downloader", NULL);
dispatch_async(fetchQ, ^{
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
[self myAsyncMultipleURLRequestFunction];
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self updateUIFunction];
});
});
});
First Configure run loop.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[NSURLConnection connectionWithRequest:request delegate:self];
while(!self.finished) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
});
Try this
In my iOS App,I have hundreds of image download.
I use AFNetworking to get those images.I want to manage the request number of AFWorking.
This is my code:
The problem is:It will block my UI.
THX for help me!
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
for (NSString *urlString in self.downloadImageList) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSString *filename = url.lastPathComponent;
NSURL *outputFileURL = APPLICATION_DOCUMENTS_DIRECTORY;
outputFileURL = [outputFileURL URLByAppendingPathComponent:[NSString stringWithFormat:#"%#/images/%#",self.boardId,filename]];
dispatch_group_async(group, dispatch_get_main_queue(), ^{
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamWithURL:outputFileURL append:YES];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.downloadImageList removeObject:urlString];
NIDINFO(#"download success %#",filename);
dispatch_semaphore_signal(semaphore);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NIDERROR(#"download image error:%#\n%#",error,urlString);
dispatch_semaphore_signal(semaphore);
}];
[operation start];
});
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
dispatch_release(group);
The problem is: It will block my UI.
Well yeah, that's sort of the point of dispatch_semaphore_t. If you remove those calls, your AFNetworking calls will execute asynchronously in the background without affecting your main / UI thread.
I don't know the full context of where this code is being used, but it looks like you may want to consider having the method that calls this take a block parameter, that can be called once all of the requests have finished. You may also want to take a look at AFHTTPClient's batch operations features, which allows you to track the progress of a group of operations, getting callbacks for each individually, and when all of then finish.