my code is :
- (NSString*)run:(NSString*)command{
_semaphore = dispatch_semaphore_create(0);
// Create and start timer
NSTimer *timer = [NSTimer timerWithTimeInterval:0.5f
target:self
selector:#selector(getState:)
userInfo:nil
repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSRunLoopCommonModes];
[runLoop run];
//and it stuck there
// Wait until signal is called
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
return _state;
}
- (void)getState:(NSTimer*)time{
// Send the url-request.
NSURLSessionDataTask* task =
[_session dataTaskWithRequest:_request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSLog(#"result: %#", data);
} else {
_state = #"error";
NSLog(#"received data is invalid.");
}
if (![_state isEqualToString:#"inProgress"]) {
dispatch_semaphore_signal(_semaphore);
// Stop timer
[timer invalidate];
}
}];
[task resume];
}
after run the code
[runLoop run];
it had nothing happened!
so, what's wrong with the code?
Calling dispatch_semaphore_wait will block the thread until dispatch_semaphore_signal is called. This means that signal must be called from a different thread, since the current thread is totally blocked. Further, you should never call wait from the main thread, only from background threads.
is that helpful?
A couple of observations:
Your use of the semaphore is unnecessary. The run won't return until the timer is invalidated.
The documentation for run advises that you don't use that method as you've outlined, but rather use a loop with runMode:beforeDate: like so:
_shouldKeepRunning = true;
while (_shouldKeepRunning && [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
// this is intentionally blank
}
Then, where you invalidate the timer, you can set _shouldKeepRunning to false.
This notion of spinning up a run loop to run a timer is, with no offense intended, a bit dated. Nowadays if I really needed a timer running on a background thread, I'd use a dispatch timer, like outlined the first half of https://stackoverflow.com/a/23144007/1271826. Spinning up a runloop for something like this is an inefficient pattern.
But let's step back and look at what you're trying to achieve, I assume you're trying to poll some server about some state and you want to be notified when it's no longer in an "in progress" state. If so, I'd adopt an asynchronous pattern, such as completion handlers, and do something like:
- (void)checkStatus:(void (^ _Nonnull)(NSString *))completionHandler {
// Send the url-request.
NSURLSessionDataTask* task = [_session dataTaskWithRequest:_request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *state = ...
if (![state isEqualToString:#"inProgress"]) {
completionHandler(#"inProgress");
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self checkStatus:completionHandler];
});
}
}];
[task resume];
}
and then call it like so:
[self checkStatus:^(NSString *state) {
// can look at `state` here
}];
// but not here, because the above is called asynchronously (i.e. later)
That completely obviates the need for run loop. I also eliminate the timer pattern in favor of the "try again x seconds after prior request finished", because you can have timer problems if one request wasn't done by the time the next timer fires. (Yes, I know you could solve that by introducing additional state variable to keep track of whether you're currently in a request or not, but that's silly.)
Related
I am trying to implement an auto save function to my app and having troubles with killing my background loop when the viewcontroller is no longer active.
This is currently what my method looks like:
-(void)saveTimer{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[NSThread sleepForTimeInterval:15];
NSLog(#"saving now");
[self save:self];
[self saveTimer];
});
}
I have read a little that I may not be able to cancel a global thread this this so I have also looked at using NSOperationQueue like this:
myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{
// Background work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Main thread work (UI usually)
}];
}];
But dont know how to cancel or destroy this either.
I'd recommend using an NSTimer instead. This way you could fire your timer like this (holding a reference to it):
timer = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(save:self:) userInfo:nil repeats:YES];
And then, when you want to stop it, just call
[timer invalidate];
timer = nil;
You can cancel your operationQueue in -dealloc() method of your viewController. As such:
(void)dealloc {
[_queue cancelAllOperations];
[_queue release];
}
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.
});
});
I'm trying to be able to call a 'completionHandler' block from inside another completionHandler block (called after an asynchronous URL request). This however results in the application crashing with the following message (I'm using Zombie objects):
*** -[CFRunLoopTimer hash]: message sent to deallocated instance
Using instruments I was able to find out that the problem is due to the block being deallocated but I can't figure out how to keep it retained for long enough. Is it because I'm calling the block from another asynchronous block? My code is below (MyCompletionHandler returns void and takes void):
-(void)requestWithRequest:(NSURLRequest*)request withCompletionHandler:(MyCompletionHandler)serverCompletionHandler{
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// do stuff…
if (serverCompletionHandler) {
dispatch_async(dispatch_get_main_queue(), ^{
serverCompletionHandler();
});
}];
}
However this code is called through another method which supplies the serverCompletionHandler parameter (could this be the problem?).
So for example, the above method would be called by this method:
-(void)createAndSendRequestWithCompletionHandler:(MyCompletionHandler)serverCompletionHandler{
NSMutableURLRequest* request = //..
[self requestWithRequest:request withCompletionHandler:serverCompletionHandler];
}
Instruments shows that a block is either released or deleted (I assume the block I am calling) which would explain the deallocated object being called.
Any help would be appreciated.
EDIT
The code for the timer (which seems to be deallocated) is:
if ([timer isValid]) {
[timer invalidate];
}
timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(createAndSendRequestWithCompletionHandler:) userInfo:nil repeats:YES];
The confusing thing is the code worked fine until I added the completionHandlers.
No thats not a problem you can acess the block within another block.I think problem is that you are already on mainQueue ([NSOperationQueue mainQueue]) and you again try to getMainQueue on mainQueue.As sendAsynchronousRequest uses NSRunloop of queue it gets deallocated when you again ask for main queue as you are already on main queue.You can check if you are already on main queue just call serverCompletionHandler else dispatch on mainqueue.You can skip this check in this case as you are sure your are main queue and can just call serverCompletionHandler()
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// do stuff…
if([[NSOperationQueue currentQueue] isEqual:[NSOperationQueue mainQueue]]){ //check for main queue
if (serverCompletionHandler) {
serverCompletionHandler();
}
}
else{
if (serverCompletionHandler) { if not than dispatch on main queue
dispatch_async(dispatch_get_main_queue(), ^{ //No need to do that
serverCompletionHandler();
});
}
}];
EDIT:Thanks of edited code.As you are using
timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(createAndSendRequestWithCompletionHandler:) userInfo:nil repeats:YES];
Now as you are not passing completionhandler so by doing this createAndSendRequestWithCompletionHandler: timer passes itself to your serverCompletionHandler.
So serverCompletionHandler itself contains timer object not any block object.If you try to NSLog serverCompletionHandler in requestWithRequest you will find it is timer object.Now when dispatch_async tries to call serverCompletionHandler as it is not block it will crash.
Write these two lines in createAndSendRequestWithCompletionHandler
NSLog(#"serverCompletionHandler obnj %#",serverCompletionHandler );
NSLog(#"class %#",NSStringFromClass([serverCompletionHandler class] ));
EDIT 2
if you really want to pass the completion handler than pass in userInfo of timer object.Use below code
#import "YourViewController.h"
typedef void (^MyCompletionHandler)(void);
#interface YourViewController ()
{
NSTimer *timer;
}
#end
#implementation YourViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
if ([timer isValid]) {
[timer invalidate];
}
MyCompletionHandler com = ^{
NSLog(#"Hi this is completion handler");
};
timer = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(createAndSendRequestWithCompletionHandler:) userInfo:#{#"serverCompletionHandler":com} repeats:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(void)requestWithRequest:(NSURLRequest*)request withCompletionHandler:(MyCompletionHandler)serverCompletionHandler{
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// do stuff…
if (serverCompletionHandler) {
dispatch_async(dispatch_get_main_queue(), ^{
serverCompletionHandler();
});
}
}];
}
-(void)createAndSendRequestWithCompletionHandler:(NSTimer *)timerObj{
// NSLog(#"serverCompletionHandler obnj %#",serverCompletionHandler );
// NSLog(#"class %#",NSStringFromClass([serverCompletionHandler class] ));
//get completion handler from `userInfo`
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
[self requestWithRequest:request withCompletionHandler:timerObj.userInfo[#"serverCompletionHandler"]];
}
#end
I'm having trouble with semaphore.
I have a serie of blocks and I want a block is executed just when the previous one has been finished its work.
I red that I have to play with gcd semaphore but the app stop working at the point signed in the code and it never enters in the block completation.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSLog(#"1. AZIENDE: BEGIN");
[Model syncAziende:^(id response, NSError *error) {
dispatch_semaphore_signal(semaphore);
NSLog(#"2. AZIENDE: FINISH");
}];
/*BLOCKS HERE */dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(#"3. AZIENDE: BEGIN");
[Model syncContatti:^(id response, NSError *error) {
NSLog(#"4. AZIENDE: FINISH");
}];
Here's the output:
2014-03-26 09:35:56.561 NSalesCDC[1071:60b] 1. AZIENDE: BEGIN
Trying to use semaphores is not the correct approach to this.
Instead, chain your callbacks together. You can create your blocks outside of each other to prevent horrible, pyramid-like callback hell.
This should work for you:
// The block that is called when syncContatti: is complete
void (^contattiBlock)(id, NSError *) = ^(id response, NSError *error) {
NSLog(#"4. AZIENDE: FINISH");
};
// The block that is called when syncAziende: is complete
void (^aziendeBlock)(id, NSError *) = ^(id response, NSError *error) {
NSLog(#"2. AZIENDE: FINISH");
// Now, we know that syncAziende: is finished, we can start the next step
[Model syncContatti:conCattiBlock];
};
// Now we begin the entire chain of events
NSLog(#"1. AZIENDE: BEGIN");
[Model syncAziende:aziendeBlock];
One downside of this is that you have to define your blocks in reverse-order, but that's not too bad.
You can use dispatch_barrier_async(). dispatch_barrier_async() will wait until all the tasks that are scheduled before the barrier to finish execution and then it will start execution. All the tasks scheduled after the barrier will wait for the barrier to finish.
dispatch_async(myQueue,
// this will start working now
});
dispatch_barrier_async(myQueue,
// this will wait until the previous block finish
//and will block the next one from execution
})
dispatch_async(myQueue,
// this will wait for the barrier to finish
});
Use it this way:
- (void) testSomethingAPI
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[Model syncAziende: ^(id response, NSError *error)
{
// Your Stuff here...
dispatch_semaphore_signal(semaphore);
}];
while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW))
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 1.f]];
}
}
You may use NSOperation dependencies.
E.g.
NSOperationQueue * que = [[NSOperationQueue alloc] init];
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"first");
}];
NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"second");
}];
[op2 addDependency:op];
[que addOperations:#[op,op2] waitUntilFinished:NO];
You can also call the second block within the first or use other guys approaches
If your reply to my comment above really is the structure of your code, it cries out for refactoring. The repetition is a good candidate for abstraction.
Perhaps something like:
static const struct {
SEL selector;
NSString* message;
} steps[] = {
{ #selector(syncAziende:), #"Sincrinizzo i contatti" }.
{ #selector(syncContatti:), #"Sincrinizzo le destinazioni" }.
// ...
};
- (void) doStep:(int) step
{
if (step < sizeof(steps) / sizeof(steps[0]))
{
[Model performSelector:steps[step].selector withObject:[^(id response, NSError *error){
hud.labelText = [NSString stringWithFormat:#"%d/%d: %#", step + 1, sizeof(steps) / sizeof(steps[0]), steps[step].message];
[self doStep:step + 1];
} copy]];
}
else
{
dispatch_async(dispatch_get_main_queue(), ^{
hud.mode = MBProgressHUDModeText;
hud.labelText = #"Sincronizzazione terminata";
[hud hide:YES afterDelay:1.5];
});
}
}
...
[self doStep:0];
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.