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
Related
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.)
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 want to call a unspecified number of URL requests which must be fired one after other. As the server can´t handle multiple requests with identical user-ID at the same time (only the last request is processed) i have to send my requests in an interval with about 1 seconds of gap. I did that within a dispatch_after block and increasing delays. But this is neither really secure nor elegant.
I´ve been just reading all day about GCD and want to try to change my code to send URL requests in a chain. My server connection class is build upon a NSURLConnection with asynchronuous request. That means it wouldn´t work with dispatch_async as the method call returns immediately back and the next request in the dispatch queue is called (which is probably immediately). But i have to wait for the response of the server until i may send the next request. My server connection class sends back via a delegate, but with dispatch_async it is never sending any deletate callbacks. Anyhow it wouldn´t work this way.
Probably it is better to put all requests into a NSArray and then call a method which will send requests from the array to the connection class and the delegate callback will pop the item from the array and sending the next request till all requests are done. Unfortunately i absolutely have no idea how i could store the requests and parameters in an array. Currently my call looks like that:
- (void)sendSettings
{
//NSLog(#"begins: %s", __FUNCTION__);
dataProtocol = [[BackgroundSoundConnection alloc] init];
[dataProtocol setDelegate:self];
//double delayInSeconds;
//dispatch_time_t popTime;
//delayInSeconds = 0.1f;
if (self.switch1.on)
{
if (![self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundNextCall/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"nextCall" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.nextCall.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundNextcall"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
if (self.switch2.on)
{
if (![self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundIncoming/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"incomingCalls" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.incomingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundIncoming"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
if (self.switch3.on)
{
if (![self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundOutgoing/%#", self.sound.globalId] httpMethod:#"PUT" sound:self.sound stickerType:#"outgoingCalls" personMSISDN:nil];
}
} else {
if ([self.pinnedSettings.outgoingCalls.globalId isEqualToString:self.sound.globalId]) {
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"disableBackgroundSoundOutgoing"] httpMethod:#"PUT" sound:nil stickerType:nil personMSISDN:nil];
}
}
for (int i = 0; i < [personArray count]; i++)
{
if (![personArray[i] connectedToServer])
{
NSLog(#"sound: %#", [personArray[i] soundId]);
NSLog(#"msisdn: %#", [personArray[i] personMSISDN]);
[dataProtocol requestDataFromServer:[NSString stringWithFormat:#"setBackgroundSoundContext/%#/%#", [personArray[i] soundId], [personArray[i] personMSISDN]] httpMethod:#"PUT" sound:self.sound stickerType:#"contextCalls" personMSISDN:[personArray[i] personMSISDN]];
}
}
[self animateViewAway:self.view];
}
A part of the request parameters is already in an array. I could use this array and push the other request parameters into it and then sending the first parameter. And after server responded send the next request triggered by the callback from the delegate. Probably this would work.
But i´m just wondering if there isn´t andy way to que the requests a dispatch queue. But how could i que the delegates as well? Or what do i have to do that the queue will wait until the server responds? I´d like to avoid rewriting my server connection class from asynchronous to synchronous URLConnection which would probably make the difference.
Can anybody point me to a solution with asynchronous URLConnection and dispatch_async?
I haven´t seen the possibilites of NSOperation and NSOperationQueue yet. In the podcast of Jeff Kelley i´ve heard that the advantage of GCD over NSOperation is the dependencies feature. http://iphreaksshow.com/042-iphreaks-show-concurrency-with-jeff-kelley/
Or did i mix up everything? What would you recommend?
A complete NSURLRequest represents a complete request by containing a path, query params or body, headers, etc. You can build several of these to represent your several server requests.
NSURLConnection provides an asynch send (sendAsynchronousRequest:queue:completionHandler:). A naive way to sequence a series of requests, is to nest the requests in completion blocks as follows...
[NSURLConnection sendAsynchronousRequest:request0 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
[NSURLConnection sendAsynchronousRequest:request1 queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
// and so on... yikes, we'll have code in column 1000 pretty soon
But it should be clear that this is a weak idea. You can get the same effect for sequencing an arbitrary number of requests with pretty compact code as follows:
- (void)doManyRequests:(NSArray *)requests withResults:(NSMutableArray *)results completion:(void (^)(void))completion {
if (!requests.count) {
return completion();
}
NSURLRequest *nextRequest = requests[0];
NSArray *remainingRequests = [requests subarrayWithRange:NSMakeRange(1, requests.count-1)];
[NSURLConnection sendAsynchronousRequest:nextRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[results addObject:data];
[self doManyRequests:remainingRequests withResults:results completion:completion];
}];
}
Now, as you suggested, prepare several requests and place them in an array:
NSURLRequest *request0 = // however you build this for a given user id
NSURLRequest *request1 = // etc.
NSURLRequest *request2 = // etc.
NSArray *requests = #[request0, request1, request2];
NSMutableArray *results = [NSMutableArray array];
[self doManyRequests:requests withResults:results completion:^{
NSLog(#"this will be an array of NSData objects %#", results);
}];
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];
[NSURLConnection sendAsynchronousRequest:req queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if(data != nil && self.superview != nil) { // <-- EXC_BAD_ACCESS on this line
[self.musicItemImg setAlpha:0.0];
[self.musicItemImg setImage:[UIImage imageWithData:data]];
[UIView animateWithDuration:0.25 animations:^{
[self.musicItemImg setAlpha:1.0];
}];
}
});
}];
Sometimes this view is removed before the async load is finished and when it tries to use self I get an EXC_BAD_ACCESS and understandably so. Can I abort an async connection? Or is there a better way to combat this?
First of all it is very bad style to start url requests in a UIView subclass. You should do that in a view controller!
The NSOperationQueue should be a class instance of the view controller. Also the views you need to configure in the completion handler must be class instances (properties). Use the same operation queue for all requests. In viewWillDisappear you can cancel all request operations with:
[self.operationQueue cancelAllOperations];