Is there a good way to call an external method after a set time limit for completing the long process outlined below? I would like the long process to stop trying after a set interval and call a method to try something else and wrap up the request.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//// LONG PROCESS
dispatch_async(dispatch_get_main_queue(), ^{
//// RESULTS PROCESS
});
});
In order to "kill" the process that's running your block, you'll have to check a condition. This will allow you to do cleanup. Consider the following modifications:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
BOOL finished = NO;
__block BOOL cancelled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5.0 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
if (!finished) {
cancelled = YES;
}
});
void (^cleanup)() = ^{
// CLEANUP
};
//// LONG PROCESS PORTION #1
if (cancelled) {
cleanup();
return;
}
//// LONG PROCESS PORTION #2
if (cancelled) {
cleanup();
return;
}
// etc.
finished = YES;
dispatch_async(dispatch_get_main_queue(), ^{
//// RESULTS PROCESS
});
});
In the ////Long Process change a boolean value (like BOOL finished) to true when finished.
After the call to dispatch_async(...) you typed here, add this:
int64_t delay = 20.0; // In seconds
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delay * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^(void){
if (!finished) {
//// Stop process and timeout
}
});
In this way, after 20 seconds (or any time you want) you can check if the process is still loading and take some provisions.
I've used this method for Swift:
let delay = 0.33 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))
dispatch_after(time, dispatch_get_main_queue()) {
//// RESULTS PROCESS
}
Related
Here is the case:
There are more than 3 async tasks. Each one can only be executed after the completion of the previous one. Yes, must be after the completion of the previous one. That's why dispatch group might not be good for this case because it doesn't care about the 'ordering' quite much. What I'm looking for is a good way of writing such code without too much nesting - just similar to what 'promisekit' does to break the nested block.
I also saw some people suggesting 'serial queue'. Well I tried using the code below:
- (void) asyncMethod1WithCompletion: (void(^)())completion
{
int64_t delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"async 1 finished");
completion();
});
}
- (void) asyncMethod2WithCompletion: (void(^)())completion
{
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"async 2 finished");
completion();
});
}
And I call those methods using serial queue:
dispatch_queue_t serialQueue = dispatch_queue_create("testqueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(#"async1 started");
[self asyncMethod1WithCompletion:^{
}];
});
dispatch_sync(serialQueue, ^{
NSLog(#"async2 started");
[self asyncMethod2WithCompletion:^{
}];
});
If everything works well, async task 2 should start executing after 5 seconds which is the completion time of async task one
However, the result is not as what I expected:
2016-09-14 18:59:39.853 SerialQueueTest[32002:2292385] async1 started
2016-09-14 18:59:39.854 SerialQueueTest[32002:2292385] async2 started
2016-09-14 18:59:41.854 SerialQueueTest[32002:2292385] async 2 finished
2016-09-14 18:59:45.353 SerialQueueTest[32002:2292385] async 1 finished
Did I write the code wrong? Is there any native way of writing lesser nesting code? If there are 5-6 async tasks and each relies on the result of each other, the code would be very massy. I would even think of one very case: a common login function which also integrates some 3rd party accounts login.
I'm also thinking of using dispatch group in a different way: 1. enter the group and finish the first async task and exit group. 2. inside the dispatch_group_notify block, enter the dispatch group again for second async task. 3. Outside the disptach_group_notify block, calling the second async task and exit the group again. 4. write the second dispatch_group_notify for notifying the completion of the second task.
- (dispatch_group_t) asyncMethod1
{
NSLog(#"async1 started");
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
int64_t delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"async 1 finished");
dispatch_group_leave(group);
});
return group;
}
- (dispatch_group_t) asyncMethod2
{
NSLog(#"async2 started");
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
int64_t delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"async 2 finished");
dispatch_group_leave(group);
});
return group;
}
And using:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
dispatch_group_notify([self asyncMethod1], queue, ^{
[self asyncMethod2];
});
});
dispatch_group_t looks similar there like promise.
As you can see, you can even use concurrent queue.
You can use NSOperationQueue here.
Create n block operations(NSBlockOperation), where n is the number of async operations you have.
Add them in the NSOperationQueue in the order you want them to be executed in.
Set maxConcurrentOperationCount of NSOperationQueue to 1 to make the queue as serial.
I'm working on the chat app, in chat screen my app need to check new message every 5s. I want to do that in background thread so my app will be not blocked UI. I tried the code below but when user typing the message, the UI seems to be blocked. Also, I cannot fire this task when user exit the chat screen.
This is my code I tried:
getLatestMessagesWithInterval() is called in viewwillAppear()
-(void) getLatestMessagesWithInterval
{
NSLog(#"GET MESSAGE INTERVAL");
[self retrieveLatestChatMessages];
// Call this method again using GCD
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, q_background, ^(void){
[self getLatestMessagesWithInterval];
});
}
-(void) retrieveLatestChatMessages
{
if([[NSUserDefaults standardUserDefaults] boolForKey:#"LoggedIn"]) {
NSDictionary * userDictionary = [[NSUserDefaults standardUserDefaults] dictionaryForKey:#"SessionDictionary"];
.....
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[LSDataManager sharedDataManager] getLatestMessagesWithAuthKey:authenKey andLimit:limit withBlock:^ (NSDictionary* responseDict)
{
if (responseDict) {
[self loadDataFromServer:responseDict];
NSArray* lastMessageArray= nil;
//filter message data
if (self.MessagesArray.count >0) {
//perform data
self.TempdataSource = [[[ContentManager sharedManager] generateConversation:lastMessageArray withSenderID:self.senderID] mutableCopy];
//compare 2 arrays
if ([self.TempdataSource count] == [self.dataSource count]) {
NSLog(#"both are same");
}
else{
NSLog(#"both are different");
self.dataSource = [self.TempdataSource mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^(){
//Add method, task you want perform on mainQueue
[self refreshMessages];
});
}
}
}
}
}];
});
}
}
I have called retrieveLatestChatMessages() to get message from server. the server will return in block. After performing the data, I reload tableview in main thread. Pls. help me to correct it. Thanks in advance.
Communicate with API asynchronously for example use AFNetworking and your UI will not be blocked, also you can set timer for every second and call something like that
if (numberOfSeconds % 5 == 0) {
numberOfSeconds = 0;
retrieveLatestChatMessages()
}
Two options
Option 1
Call that method every 5 seconds using NSTimer.
I would recommend not to use it.
Option 2
Have an app TCP based for chat messages.
This would be best option for chat messages.
Edit 1
learn how to use TCP as per your language. For iOS TCP follow link
http://www.tekritisoftware.com/sites/default/files/Socket_Programing_for_IOS.pdf
Is it possible to make this "clearer" or "better" ?
Any solution is welcome, even thought i got a right answer.
The problem is dispatch_after() with popTime==0 is still giving time to the main thread to make some UI changes. The following code is sometimes called from a background thread.
-(void)methodCalledFromSomeThread{
if (delayInSeconds) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Updating some UI like
});
}else{
dispatch_async(dispatch_get_main_queue(), ^{
// Updating some UI like
});
}
}
dispatch_async on the main queue will also queue the code to run on the next runloop, giving time for the system to update the UI. You can check like so:
void (^block)() = ^{
[self.tableView reloadData];
};
if (delayInSeconds) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), block);
}else if([NSThread isMainThread]) {
block();
}
else {
dispatch_async(dispatch_get_main_queue(), block);
}
I am developing an app where i want to call method in separate queue using dispatch_async. I want to call that method repeatedly after certain interval of time. But the method is not getting called.
I don't know whats wrong. Here is my code:
dispatch_async( NotificationQueue, ^{
NSLog(#"inside queue");
timer = [NSTimer scheduledTimerWithTimeInterval: 20.0
target: self
selector: #selector(gettingNotification)
userInfo: nil
repeats: YES];
dispatch_async( dispatch_get_main_queue(), ^{
// Add code here to update the UI/send notifications based on the
// results of the background processing
});
});
-(void)gettingNotification {
NSLog(#"calling method ");
}
If you want a repeating timer to be invoked on a dispatch_queue_t, use dispatch_source_create with a DISPATCH_SOURCE_TYPE_TIMER:
dispatch_queue_t queue = dispatch_queue_create("com.firm.app.timer", 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 20ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// stuff performed on background queue goes here
NSLog(#"done on custom background queue");
// if you need to also do any UI updates, synchronize model updates,
// or the like, dispatch that back to the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"done on main queue");
});
});
dispatch_resume(timer);
That creates a timer that runs once every 20 seconds (3rd parameter to dispatch_source_set_timer), with a leeway of a one second (4th parameter to dispatch_source_set_timer).
To cancel this timer, use dispatch_source_cancel:
dispatch_source_cancel(timer);
Try this code
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async( dispatch_get_main_queue(), ^{
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self
selector: #selector(gettingNotification) userInfo: nil repeats: YES];
// Add code here to update the UI/send notifications based on the
// results of the background processing
});
});
-(void)gettingNotification {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//background task here
dispatch_async( dispatch_get_main_queue(), ^{
// update UI here
);
});
}
int parameter1 = 12;
float parameter2 = 144.1;
// Delay execution of my block for 10 seconds.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC), dispatch_get_current_queue(), ^{
NSLog(#"parameter1: %d parameter2: %f", parameter1, parameter2);
});
look for more
How do you trigger a block after a delay, like -performSelector:withObject:afterDelay:?
I found that sometimes you may be doing something in your iPhone application that requires the user to wait while it completes. Often this is a network related activity, but in other cases it may not be. In my case I was parsing the response from a network connection and wanted the network activity indicator to keep spinning even though it had already downloaded the content.
below is what i'm doing:
applicationDelegate.m :
- (void)setNetworkActivityIndicatorVisible:(BOOL)setVisible
{
static NSInteger NumberOfCallsToSetVisible = 0;
if (setVisible)
NumberOfCallsToSetVisible++;
else
NumberOfCallsToSetVisible--;
// Display the indicator as long as our static counter is > 0.
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:(NumberOfCallsToSetVisible > 0)];
}
otherView.m:
dispatch_queue_t dataLoadingQueue = dispatch_queue_create("synchronise", NULL);
dispatch_async(dataLoadingQueue,
^{
[appDelegate setNetworkActivityIndicatorVisible:YES];
[[DataLoader instance]LoadDataForGrewal];
[[FieldConsultantViewModelManager instance] resetCache];
[[DailyFieldConsultantViewModelManager instance] clearCache];
[appDelegate loadMainViews];
[[DataLoader instance]LoadDataForOtherEntities];
[appDelegate setNetworkActivityIndicatorVisible:NO];
});
dispatch_release(dataLoadingQueue);
as u can see above, i'm trying to keep the network indicator while updating the data into the database but it does not work , any clue / suggestions ?
Thanks
EDIT :
dispatch_queue_t dataLoadingQueue = dispatch_queue_create("synchronise", NULL);
dispatch_async(dataLoadingQueue,
^{
dispatch_async(dispatch_get_main_queue(), ^{ [self setNetworkActivityIndicatorVisible:YES]; });
[[DataLoader instance]LoadDataForGrewal];
[[FieldConsultantViewModelManager instance] resetCache];
[[DailyFieldConsultantViewModelManager instance] clearCache];
[appDelegate loadMainViews];
[[DataLoader instance]LoadDataForOtherEntities];
dispatch_async(dispatch_get_main_queue(), ^{ [self setNetworkActivityIndicatorVisible:NO]; });
});
dispatch_release(dataLoadingQueue);
it does not work i'm not sure why because i'm newbie in ios
try to dispatch your setNetworkActivityIndicatorVisible: call on main queue, because UIApplication is in UIKit and UIKit is not thread-safe.
For anyone looking for a complete solution, this is what I use (in Swift). It delays a short duration when stopping the network indicator. This will prevent the indicator from flickering if you have a lot of serial network request.
private var networkActivityCount = 0
func updateNetworkActivityCount(increaseNumber increase:Bool)
{
let appendingCount = increase ? 1 : -1
networkActivityCount += appendingCount
let delayInSeconds = increase ? 0.0 : 0.7
perform({ () -> () in
let application = UIApplication.sharedApplication()
let shouldBeVisible = self.networkActivityCount > 0
if application.networkActivityIndicatorVisible != shouldBeVisible {
application.networkActivityIndicatorVisible = shouldBeVisible
}
}, afterDelay: delayInSeconds)
}
Where perform:afterDelay is defined as
public func perform(block: ()->(), afterDelay:Double) {
let dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(afterDelay * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
block()
})
}
Just call updateNetworkActivityCount(increaseNumber: true) before your request and the same with false in your request callback. I.e:
updateNetworkActivityCount(increaseNumber: true)
let task = urlSession.dataTaskWithRequest(request) { data, response, error in
self.updateNetworkActivityCount(increaseNumber: false)
...