Calling method on NSOperation subclass from another thread - ios

I've made an NSOperation subclass with a start method containing a call to a method which has a completion block. The completion block contains code which marks the operation as finished (KVO). That block seems to typically be executed on the main thread (rather than my NSOperationQueue's background thread).
What I'm seeing is that the operation doesn't appear to ever get marked as finished, and I assume this is because of the threading issue. Is there some way I can get the KVO calls to occur on the correct thread, and thus allow my operation to terminate properly?
The method with the completion block is 3rd party code, so I'd prefer not to have to alter it.
EDIT: Here's the relevant code.
-(void)start
{
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
NSData *mutableData = [NSData thisIsWhereMyDataIsCreated];
[self.peripheral writeData:mutableData characteristicUUID:[CBUUID transmitCharacteristicUUID] serviceUUID:[CBUUID serviceUUID] completion:^(CBCharacteristic * _Nullable characteristic, NSError * _Nullable error) {
[self markAsFinished];
}];
}
-(void)markAsFinished
{
NSLog(#"Finishing op...");
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
self.onComplete();
}
And it's the markAsFinished: method that's being called on the main thread, via the completion block for the writeData... method.

Related

Execute a block asynchronously before statement in same method

How to execute a block asynchronously before statement in same method?
The return always execute before block, but if that the commit always equals to No.
I want the block execute before return.
How can I do it?
I try dispatch_semaphore_t but checkVerifyCode is in main thread.
I can't block the main thread.
-(BOOL)checkVerifyCode
{
__block BOOL commit = NO;
[SMSSDK commitVerificationCode:self.verificationNum.text phoneNumber:self.phoneNumber.text zone:#"86" result:^(NSError *error) {
if (error) {
NSString *errInfo = [error.userInfo objectForKey:#"commitVerificationCode"];
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
[hud setMode:MBProgressHUDModeText];
[hud setLabelText:#"验证码输入错误"];
[hud setLabelText:errInfo];
hud.color = [UIColor clearColor];
hud.labelColor = [UIColor colorWithRed:118/255.f green:214/255.f blue:255/255.f alpha:0.8f];
hud.detailsLabelColor = [UIColor colorWithRed:118/255.f green:214/255.f blue:255/255.f alpha:0.8f];
hud.margin = 10.f;
hud.yOffset = -100.f;
hud.removeFromSuperViewOnHide = YES;
[hud hide:YES afterDelay:3];
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:commit] forKey:#"bool"];
[self performSelectorOnMainThread:#selector(setCommit:) withObject:dict waitUntilDone:NO];
}else
{
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:commit] forKey:#"bool"];
[self performSelectorOnMainThread:#selector(setCommit:) withObject:dict waitUntilDone:NO];
commit = YES;
}
}];
return commit;
}
You are effectively asking to make an asynchronous method synchronous. And since your call to the checkVerifyCode method is on the main thread, that would require blocking the main thread (which, as noted, is bad idea).
Instead, you should move to having some method somewhere which you can call to update based on the result of the asynchronous method.
I.e.:
put up progress indicator and a field that says "Checking your code"
modify checkVerifyCode to return void and
at the end of the asynchronous call, call some method somewhere:
_
[self _checkVerifyDone:commit];
And, if you really need it on the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
[self _checkVerifyDone:commit];
});
You can dispatch the entire function to a separate queue, then use dispatch_semaphore to block that queue until the result is returned.
What's your higher level goal here? It seems like a dispatch_notify_group might be what you're looking for, but it's hard to tell from your question. I understand the problem you're trying to solve, but what is the big picture?
Rather than return the value 'commit' like you are currently doing, pass into the method a completion block that takes a 'bool' as a parameter. Then the caller would be able to pass code into 'verifyCheckCode' and have it asynchronously executed.

How to do async task in non-concurrent custom nsoperation

Maybe I let NSOperation to play a wrong role in non-concurrent job. My requirement is , I want to do a lot of async jobs, but I want them to be completed in order. When task1 is finished after the async callback, task2 can be take into work now. And I make all the task a NSOperation. However, NSOperation is used to multiple thread programming most time. Is my choice wrong. But it remind me to think more about the NSOperation in this case, we can't manage manually the isFinished and isExecute in a sync block since the operation have been release in non-concurrent nsoperation,it means i couldnot use the powerful operation queue to automatically manage the task.Any idea?Thanks for your answer..
edit with code :
-(void)main {
[super main];
self.isOperationExcuting = YES;
self.isOperationFinished = NO;
WEAKSELF
[self query:^(NSArray *array, NSError *error) {
//I set my custom property, but it do not cause my NSOperation to be finished
weakSelf.isOperationFinished = YES;
weakSelf.isOperationExcuting = NO;
}];
}
-(void)query:(void (^)(NSArray *array, NSError *error))block {
BmobQuery *query = [BmobQuery queryWithClassName:#"Room"];
[query findObjectsInBackgroundWithBlock:block];
}
-(BOOL)isFinished {
return self.isOperationFinished;
}
- (BOOL)isExecuting {
return self.isOperationExcuting;
}
- (void)start {
[super start];
NSLog(#"start");
}
- (void)cancel {
[super cancel];
NSLog(#"cancel");
}
Just make all added operations serial by setting maxConcurrentOperationCount to 1.
NSOperationQueue* queue = [[ NSOperationQueue alloc ] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation1];
[queue addOperation:operation2];
[queue addOperation:operation3];
NSOperationQueue
The NSOperationQueue class regulates the execution of a set of
NSOperation objects. After being added to a queue, an operation
remains in that queue until it is explicitly canceled or finishes
executing its task.

NSOperation + NSURLConnection

I have created NSOperation class in that class i am calling NSURLConnection to fetch some data.
I am calling NSURLConnection using main thread inside NSOperation class.
NSURLConnection's delegate is set to NSOperation class object.
Call from NSURLConnection comes on main thread.
I need to process this data using the same operation thread. how do i achieve this ??
#implementation ModelCreationSearchOperation {
int try;
}
- (BOOL)isConcurrent
{
return YES;
}
- (void)start
{
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
dispatch_async(dispatch_get_main_queue(), ^{
if (self.isCancelled) {
[self finish];
return;
}
});
[self fetchData];
}
-(void)fetchData {
dispatch_async(dispatch_get_main_queue(), ^{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
});
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
[self cancel];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
//Main thread
//Want to perform parsing of response data on operation thread ....
}
You say that you "want to perform parsing of response data on operation thread." Do you really need to run it on the operation thread, or do you just need to get it off the main thread? The operation queue doesn't necessarily have a single, dedicated thread, so the question doesn't quite make sense. (It's one of the beauties of dispatch queues and operation queues, that it manages the threads for us and we generally don't have to get involved in those details.)
If you simply want the code in connectionDidFinishLoading to run on a background thread (if, for example, you're doing something exceptionally slow in this delegate method), just dispatch it to a background thread (you could use a global queue for that). If you want a serial queue for these connectionDidFinishLoading calls, create your own serial queue for that and dispatch this code to that queue. But if it's not too computationally intensive (e.g. parsing JSON or something like that), you can often just let it run on the main thread without incident.
As an aside, you can, if you really want, create a dedicated thread for your NSURLConnection delegate calls, and schedule the connection on that thread, but it's generally overkill. But see AFNetworking code for example of this implementation. This is illustrated in How do I start an Asychronous NSURLConnection inside an NSOperation?

Wait for an async methods to finish in a for loop

I have a for loop containing three asynchronous methods, and I want to make some treatment after this 3 async methods are finished.
-(void)getAllUsersInformations{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for(User *user in users){
[self getUserInfo:user];
}
//Here, I want to reload the table view for example, after finishing the for loop (executing the whole three methods).
});
}
-(void)getUserInfo:(User*)user{
[self getInformations:user];
[self getExperiences:user];
[self getEducation:user];
}
Do you have any technic to have this result?
Thank you very much.
One GCD approach is to use dispatch_group. So, before you start an asynchronous task, call dispatch_group_enter, and then when the asynchronous task finishes, call dispatch_group_leave, and you can then create a dispatch_group_notify which will be called when the asynchronous tasks finish. You can marry this with a completion block pattern (which is a good idea for asynchronous methods, anyway):
If getInformations, getExperiences and getEducation are, themselves, all asynchronous methods, the first thing you need is some mechanism to know when they're done. A common solution is to implement a completion block pattern for each. For example:
// added completionHandler parameter which will be called when the retrieval
// of the "informations" is done.
- (void)getInformations:(User*)user completionHandler:(void (^)(void))completionHandler {
// do whatever you were before, but in the asynchronous task's completion block, call this
// completionHandler()
//
// for example
NSURLRequest *request;
[NSURLConnection sendAsynchronousRequest:request queue:nil completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// handle the request here
// the important thing is that the completion handler should
// be called _inside_ the this block
if (completionHandler) {
completionHandler();
}
}];
}
Repeat this process for getExperiences and getEducation, too.
Then, you can use a dispatch group to notify you of when each of these three requests are done done, calling a completion block in getUserInfo when that takes place:
// added completion handler that will be called only when `getInformations`,
// `getExperiences` and `getEducation` are all done.
//
// this takes advantage of the completion block we added to those three
// methods above
- (void)getUserInfo:(User*)user completionHandler:(void (^)(void))completionHandler {
dispatch_group_t group = dispatch_group_create();
// start the three requests
dispatch_group_enter(group);
[self getInformations:user completionHandler:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self getExperiences:user completionHandler:^{
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[self getEducation:user completionHandler:^{
dispatch_group_leave(group);
}];
// this block will be called asynchronously only when the above three are done
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (completionHandler) {
completionHandler();
}
});
}
And you then repeat this process at the getAllUsersInformations:
// call new getUserInfo, using dispatch group to keep track of whether
// all the requests are done
-(void)getAllUsersInformations {
dispatch_group_t group = dispatch_group_create();
for(User *user in users){
dispatch_group_enter(group);
[self getUserInfo:user completionHandler:^{
dispatch_group_leave(group);
}];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
Two final thoughts:
Having outlined all of that, I must confess that I would probably wrap these requests in concurrent/asynchronous custom NSOperation subclasses instead of using dispatch groups. See the "Configuring Operations for Concurrent Execution" section of the Concurrency Programming Guide. This is a more radical refactoring of the code, so I won't tackle that here, but it lets you constrain the number of these requests that will run concurrently, mitigating potential timeout issues.
I don't know how many of these user requests are going on, but you might want to consider updating the UI as user information comes in, rather than waiting for everything to finish. This is, again, a more radical refactoring of the code, but might lead to something that feels more responsive.
Try to do a block with completion, you can't do this with a for loop if the methods are async. you have to call getUserInfo one by one after the completion of the previous. I think this gonna be solved your problem.
-(void)getAllUsersInformations{
[self registerUserAtIndex:0];
}
- (void) registerUserAtIndex: (NSInteger ) userIndex
{
RegisterOperation *op = [[RegisterOperation alloc] initWithUser:[users objectAtIndex:userIndex]];
[RegisterOperation setResultCompletionBlock:^(BOOL *finished, NSInteger userIndex) {
dispatch_async(dispatch_get_main_queue(), ^{
if (userIndex++ < [users count] {
[self registerUserAtIndex:userIndex++];
} else {
[myTableView reloadData];
}
}];
[[NSOperationQueue mainQueue] addOperation:op];
}
Hope this will help you.
Rop Answer with swift:
func processData()
{
let group: dispatch_group_t = dispatch_group_create()
for item in data as! Object {
dispatch_group_enter(group)
item.process(completion: {() -> (Void) in
dispatch_group_leave(group)
})
}
dispatch_group_notify(group, dispatch_get_main_queue(), {
//Do whatever you want
})
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Background work
for(User *user in users){
[self getUserInfo:user];
}
dispatch_async(dispatch_get_main_queue(), ^{
//reload tableview , this is on main thread.
});
});

How to timeout an asynchronous method when ran synchronously

This is essentially what I'm doing to run an asynchronous method synchronously:
This essentially works when called once, but when called multiple times, it will eventually stay inside the while loop and never get signaled. Any ideas on how to set a timer to eventually time out after sometime?
__block SomeClass *result = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
if (!error) {
result = (SomeClass *)ResponseObject;
}
dispatch_semaphore_signal(semaphore);
}];
});
// wait with a time limit
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0]];
}
dispatch_release(semaphore);
Thanks
That looks kind of like GCD abuse to me. ;) Are you running the run loop because this is executing on the main thread? Why not just use a dispatch_async() from your completion handler to invoke a handler on the main thread? eg:
- (void)handleDataReady: (id) results error: (NSError *) error {
// update your app
}
- (void)performAsyncUpdate {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleDataReady:responseObject error:error];
}];
});
}
If you really want to make it synchronous, i.e. blocking the calling thread until the operation completes then use the following pattern (of course you want to avoid blocking threads if possible)
NSCondition *waitCondtion = [NSCondition new];
__block BOOL completed = NO;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0UL);
dispatch_async(queue, ^{
[[SomeManager sharedInstance] someMethodWithCallback:^(id responseObject, NSError *error) {
if (!error) {
result = (SomeClass *)ResponseObject;
}
[waitCondtion lock];
completed = YES;
[waitCondition signal];
[waitCondition unlock];
}];
});
[waitCondtion lock];
if (!completed)
[waitCondtion wait];
[waitCondition unlock];
You can also use "waitUntilDate:" to timeout the wait after a period.
However, this pattern only works as long as the "someMethodWithCallback does not call its callback block on the same thread that is being blocked. I have copied your code because it is not obvious how "someMethodWithCallback" is implemented. Since this method is using an asynchronous pattern, then it must be doing something asynchronously therefore why are you calling it inside a dispatch_async? What thread will it call its callback block on?
You should "fill" the completion handler with whatever code you require to process the result when the completion handler finished (and also completely removing that run loop).
In order to "abort" an asynchronous operation, you should provide a cancel message which you send the asynchronous result provider.
In your case, since you have a singleton, the cancel message would have to be send like this:
[[SomeManager sharedInstance] cancel];
When the operation receives the cancel message, it should as soon as possible abort its task and call the completion handler with an appropriate NSError object indicating that it has been cancelled.
Note, that cancel messages may be asynchronous - that means, when it returns, the receiver may still execute the task.
You may achieve a "timeout" with setting up a timer, which sends the cancel message the operation, unless it has been invalidated when the operation finished.

Resources