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];
}
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 have 3 function in my code.
The code i did for manage queue for function execution.
[self firstMethodWithOnComplete:^{
[self SecongMethodWithOnComplete:^{
dispatch_async(dispatch_get_main_queue(), ^{
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(CallThirdMethod) userInfo:nil repeats:NO];
});
}];
}];
First And Second Function
- (void)firstMethodWithOnComplete:(void (^)(void))onComplete {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//processing here.....
[self CallFirstMethod];
onComplete();
});
}
- (void)SecongMethodWithOnComplete:(void (^)(void))onComplete {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//processing here.....
[self CallSecondMethod];
onComplete();
});
}
The problem is i am unable to manage their execution. I want execution order such in a way that second function only execute if first is over and third execute if second execution over.
Please help me to solve this or give any appropriate suggestions.
You can use dispatch groups for this kind of requirement, Below i am posting example code which i have used
__block NSError *configError = nil;
__block NSError *preferenceError = nil;
// Create the dispatch group
dispatch_group_t serviceGroup = dispatch_group_create();
dispatch_group_enter(serviceGroup);
// Start the first async service
dispatch_group_leave(serviceGroup);
dispatch_group_enter(serviceGroup);
// Start the second async service
dispatch_group_leave(serviceGroup);
dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
//Update UI in this block of code
});
I used below code for calling image posting first and then text posting upon completion of image posting method...
-(IBAction)btnChooseClecked:(id)sender
{
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
[queue addOperationWithBlock:^{
[self sendImage];
}];
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];//2.0
[self sendText];
}];
///(OR) i used below code also
dispatch_async(dispatch_get_main_queue(), ^{
[self sendImage];
// inside sendImage method I am calling sendText as [self performSelector:#selector(sendText) withObject:nil afterDelay:0.2];
})
}
-(void)sendImage
{
NSData *imageData = UIImageJPEGRepresentation([appDelegate scaleAndRotateImage:imageSelected.image], 0.0);
[appDelegate.hub invoke:#"Send" withArgs:#[([imageData respondsToSelector:#selector(base64EncodedStringWithOptions:)] ? [imageData base64EncodedStringWithOptions:kNilOptions] : [imageData base64Encoding])]];
[self performSelector:#selector(sendText) withObject:nil afterDelay:0.5];
}
But sometimes it is working fine but sometimes text is posting first instead of image. No delegate method is called after completion of first..As we used different API for performing post method.
Please suggest any ideas where I am going wrong..Any alternatives for above..
Thanks in Advance..
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 have a function that connects to the internet and then refreshes the cells in the table.
My function is:
- (void) updateMethod
{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = #"Data request queue";
[queue addOperationWithBlock:^{
//neither of these delay the responsiveness of the table
[columnArrayBackground removeAllObjects];
[self getColumnDataBackground];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
for (int i=0; i < 6; i++) {
columnArray[i] = columnArrayBackground[i];
};
//THIS ONE LINE CAUSES DELAYS
[homeTable reloadData];
}];
}];
}
Everything is super speedy EXCEPT for [homeTable reloadData]. When I comment that out, I have quick response. When I uncomment it, my cell response lags sometimes by a few seconds!
My other reloadData calls do not delay my app. Am I not implementing NSOperationQueue correctly?
Remove the queues from your update method and leave only what updates the table. Under the action that refreshes your table add this code. Where the updateMethod is the code that updates the table. Only when you are back on the main thread do you reload the data. Hope this helps!
//perform on new thread to avoid the UI from freezing
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),
^{
//background thread code
[self performSelector:#selector(updatemethod:) withObject:nil];
dispatch_async(dispatch_get_main_queue(),
^{ //back on main thread
[self.tableView reloadData];
});});