Suppose I run this code:
__block int step = 0;
__block dispatch_block_t myBlock;
myBlock = ^{
if(step == STEPS_COUNT)
{
return;
}
step++;
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
dispatch_after(delay, dispatch_get_current_queue(), myBlock);
};
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
dispatch_after(delay, dispatch_get_current_queue(), myBlock);
The block is invoked once from outside. When the inner invocation is reached, the program crashes without any details. If I use direct invocations everywhere instead of GCD dispatches, everything works fine.
I've also tried calling dispatch_after with a copy of the block. I don't know if this was a step in the right direction or not, but it wasn't enough to make it work.
Ideas?
When trying to solve this problem, I found a snippet of code that solves much of the recursive block related issues. I have not been able to find the source again, but still have the code:
// in some imported file
dispatch_block_t RecursiveBlock(void (^block)(dispatch_block_t recurse)) {
return ^{ block(RecursiveBlock(block)); };
}
// in your method
dispatch_block_t completeTaskWhenSempahoreOpen = RecursiveBlock(^(dispatch_block_t recurse) {
if ([self isSemaphoreOpen]) {
[self completeTask];
} else {
double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), recurse);
}
});
completeTaskWhenSempahoreOpen();
RecursiveBlock allows for non-argument blocks. It can be rewritten for single or multiple argument blocks. The memory management is simplified using this construct, there is no chance of a retain cycle for example.
My solution was derived entirely from Berik's, so he gets all the credit here. I just felt that a more general framework was needed for the "recursive blocks" problem space (that I haven't found elsewhere), including for the asynchronous case, which is covered here.
Using these three first definitions makes the fourth and fifth methods - which are simply examples - possible, which is an incredibly easy, foolproof, and (I believe) memory-safe way to recurse any block to arbitrary limits.
dispatch_block_t RecursiveBlock(void (^block)(dispatch_block_t recurse)) {
return ^() {
block(RecursiveBlock(block));
};
}
void recurse(void(^recursable)(BOOL *stop))
{
// in your method
__block BOOL stop = NO;
RecursiveBlock(^(dispatch_block_t recurse) {
if ( !stop ) {
//Work
recursable(&stop);
//Repeat
recurse();
}
})();
}
void recurseAfter(void(^recursable)(BOOL *stop, double *delay))
{
// in your method
__block BOOL stop = NO;
__block double delay = 0;
RecursiveBlock(^(dispatch_block_t recurse) {
if ( !stop ) {
//Work
recursable(&stop, &delay);
//Repeat
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), recurse);
}
})();
}
You'll note that in the following two examples that the machinery of interacting with the recursion mechanism is extremely lightweight, basically amounting to having to wrap a block in recurse and that block must take a BOOL *stop variable, which should be set at some point to exit recursion (a familiar pattern in some of the Cocoa block iterators).
- (void)recurseTo:(int)max
{
__block int i = 0;
void (^recursable)(BOOL *) = ^(BOOL *stop) {
//Do
NSLog(#"testing: %d", i);
//Criteria
i++;
if ( i >= max ) {
*stop = YES;
}
};
recurse(recursable);
}
+ (void)makeSizeGoldenRatio:(UIView *)view
{
__block CGFloat fibonacci_1_h = 1.f;
__block CGFloat fibonacci_2_w = 1.f;
recurse(^(BOOL *stop) {
//Criteria
if ( fibonacci_2_w > view.superview.bounds.size.width || fibonacci_1_h > view.superview.bounds.size.height ) {
//Calculate
CGFloat goldenRatio = fibonacci_2_w/fibonacci_1_h;
//Frame
CGRect newFrame = view.frame;
newFrame.size.width = fibonacci_1_h;
newFrame.size.height = goldenRatio*newFrame.size.width;
view.frame = newFrame;
//Done
*stop = YES;
NSLog(#"Golden Ratio %f -> %# for view", goldenRatio, NSStringFromCGRect(view.frame));
} else {
//Iterate
CGFloat old_fibonnaci_2 = fibonacci_2_w;
fibonacci_2_w = fibonacci_2_w + fibonacci_1_h;
fibonacci_1_h = old_fibonnaci_2;
NSLog(#"Fibonnaci: %f %f", fibonacci_1_h, fibonacci_2_w);
}
});
}
recurseAfter works much the same, though I won't offer a contrived example here. I am using all three of these without issue, replacing my old -performBlock:afterDelay: pattern.
It looks like there are no problem except delay variable. The block uses always the same time that is generated at line 1. You have to call dispatch_time every time if you want to delay dispatching the block.
step++;
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
dispatch_after(delay, dispatch_get_current_queue(), myBlock);
};
EDIT:
I understand.
The block is stored in stack by the block literal. myBlock variable is substituted for the address of the block in stack.
First dispatch_after copied the block from myBlock variable that is the address in stack. And this address is valid at this time. The block is in the current scope.
After that, the block is scoped out. myBlock variable has invalid address at this time. dispatch_after has the copied block in heap. It is safe.
And then, second dispatch_after in the block tries to copy from myBlock variable that is invalid address because the block in stack was already scoped out. It will execute corrupted block in stack.
Thus, you have to Block_copy the block.
myBlock = Block_copy(^{
...
});
And don't forget Block_release the block when you don't need it any more.
Block_release(myBlock);
Opt for a custom dispatch source.
dispatch_queue_t queue = dispatch_queue_create( NULL, DISPATCH_QUEUE_SERIAL );
__block unsigned long steps = 0;
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
dispatch_source_set_event_handler(source, ^{
if( steps == STEPS_COUNT ) {
dispatch_source_cancel(source);
return;
}
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2);
dispatch_after(delay, queue, ^{
steps += dispatch_source_get_data(source);
dispatch_source_merge_data(source, 1);
});
});
dispatch_resume( source );
dispatch_source_merge_data(source, 1);
I think you have to copy the block if you want it to stick around (releasing it when you don't want it to call itself anymore).
Related
Is it possible to delay only the return value and not the whole method like:
-(CGFloat)getValue
{
// code implementation
return floatvalue; // return float value with some delay.
}
Use a block to call back with return value with delay;
- (void)ayncGetValue:(void(^)(id value))returnBlock {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 10.f * NSEC_PER_SEC); // delay 10 seconds
dispatch_after(popTime, dispatch_get_main_queue(), ^{
returnBlock(#"hello block");
});
}
// Usage
[obj ayncGetValue:^(id value) {
// continue
}];
What you can do is. Keep this function as normal function :
-(void)getValue
{
// code implementation
[self performSelector:#selector(returnData) withObject:nil afterDelay:0.5];
}
Add this new function which will return data :
-(CGFloat)returnData
{
return floatvalue;
}
The only important thing here will be you'll have to declare floatvalue variable as class lever and not function lever.
-(void)getValue
{
// code implementation
[self performSelector:#selector(getValue:) withObject:value afterDelay:2];
}
-(CGFloat)getValue:(CGFloat *)float1
{
return float1;
}
I have a method that takes several params, I need to delay a portion of that method. I DO NOT want to split it into several methods and use [self performSelectorAfterDelay] because the delay requires params already in that method. I need something like the following
-(void)someMethod{
.....
delay {
more code but not a separate self method
}
... finish method
}
The dispatch_after function seems to line up with what you need:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void) {
// this code is going to be executed, on the main queue (or thread) after 2.0 seconds.
});
Of course, the time is configureable, and it's a bit confusing to read at first, but once you get used to how blocks work in conjunction with objective-c code, you should be good to go.
One word of caution:
NEVER, NEVER, NEVER! Block the main thread of an iPhone app using sleep(). Just don't do it!
Looks like an overkill.
-(void)someMethod{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(#"Start code");
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_sync(backgroundQueue, ^{
sleep(5);
// delayed code
NSLog(#"Delayed code");
});
dispatch_sync(backgroundQueue, ^{
// finishing code
NSLog(#"Finishing code");
});
});
}
backgroundQueue might be user at external dispatch call. It looks really bad though :)
Scenario:
User taps a button asking for some kind of modification on address book.
A method is called to start this modification and an alert view is shown.
In order to show the alert view and keep the UI responsive, I used dispatch_queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
// Show the alert view
});
});
Start the process of address book modification using:
dispatch_async(modifyingAddressBookQueue, ^{});
Now, I want to provide the user with the ability to cancel the process anytime (of course before saving the address book). So when he taps the cancel button in the alert sheet, I want to access the dispatch block, set some certain BOOL to stop the process and revert the address book.
The problem is, you can't do that! you can't access the block and change any variable inside it since all variables are copied only once. Any change of variables inside the block while being executed won't be seen by the block.
To sum up: How to stop a going operation using a UI event?
Update:
The code for the process:
- (void) startFixingModification {
_fixContacts = YES;
__block BOOL cancelled = NO;
dispatch_queue_t modifyingAddressBookQueue;
modifyingAddressBookQueue = dispatch_queue_create(sModifyingAddressBookQueueIdentifier,
NULL);
dispatch_async(modifyingAddressBookQueue, ^{
for (NSMutableDictionary *contactDictionary in _contactArray) {
if (!cancelled) {
break;
}
i = i + 1;
BOOL didFixContact = [self fixNumberInContactDictionary:contactDictionary];
if (!didFixContact) {
_fixedNumbers = _fixedNumbers - 1;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
[self setAlertViewProgress:i];
});
});
}
});
cancelledPtr = &cancelled;
}
Code for alertview (my own lib) delegate
- (void) alertViewProgressCancel:(ASAlertViewProgress *)alertView { // This is a private lib.
if (cancelledPtr)
{
NSLog(#"stopping");
*cancelledPtr = YES;
}
}
In interface, I declare
BOOL* cancelledPtr;
Update 2:
It's getting really frustrating! for the following code
for (NSMutableDictionary *contactDictionary in _contactArray) {
NSLog(#"%d", _cancelModification);
if (_cancelModification) {
break;
}
}
if _cancelModification is set to YES, the for loop is broken and that's OK. Once I comment out the NSLog line, the _cancelModification is neglected when it changes to YES!
If you declare your BOOL using __block, then it can be changed outside of the block execution, and the block will see the new value. See the documentation for more details.
An example:
#interface SNViewController ()
{
BOOL* cancelledPtr;
}
#end
#implementation SNViewController
- (IBAction)start:(id)sender
{
__block BOOL cancelled = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!cancelled) {
NSLog(#"running");
sleep(1);
}
NSLog(#"stopped");
});
cancelledPtr = &cancelled;
}
- (IBAction)stop:(id)sender
{
if (cancelledPtr)
{
NSLog(#"stopping");
*cancelledPtr = YES;
}
}
#end
Alternatively, use an ivar in your class to store the BOOL. The block will implicitly make a copy of self and will access the ivar via that. No need for __block.
#interface SNViewController ()
{
BOOL cancelled;
}
#end
#implementation SNViewController
- (IBAction)start:(id)sender
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
while (!cancelled) {
NSLog(#"running");
sleep(1);
}
NSLog(#"stopped");
});
}
- (IBAction)stop:(id)sender
{
NSLog(#"stopping");
cancelled = YES;
}
#end
Approach 1
Create a custom dispatch_async method that returns a "cancelable" block.
// The dispatch_cancel_block_t takes as parameter the "cancel" directive to suspend the block execution or not whenever the block to execute is dispatched.
// The return value is a boolean indicating if the block has already been executed or not.
typedef BOOL (^dispatch_cancel_block_t)(BOOL cancelBlock);
dispatch_cancel_block_t dispatch_async_with_cancel_block(dispatch_queue_t queue, void (^block)())
{
__block BOOL execute = YES;
__block BOOL executed = NO;
dispatch_cancel_block_t cancelBlock = ^BOOL (BOOL cancelled) {
execute = !cancelled;
return executed == NO;
};
dispatch_async(queue, ^{
if (execute)
block();
executed = YES;
});
return cancelBlock;
}
- (void)testCancelableBlock
{
dispatch_cancel_block_t cancelBlock = dispatch_async_with_cancel_block(dispatch_get_main_queue(), ^{
NSLog(#"Block 1 executed");
});
// Canceling the block execution
BOOL success1 = cancelBlock(YES);
NSLog(#"Block is cancelled successfully: %#", success1?#"YES":#"NO");
// Resuming the block execution
// BOOL success2 = cancelBlock(NO);
// NSLog(#"Block is resumed successfully: %#", success2?#"YES":#"NO");
}
Approach 2
Defining a macro for executing a block asynchronously if a condition is validated:
#define dispatch_async_if(queue,condition,block) \
dispatch_async(queue, ^{\
if (condition == YES)\
block();\
});
- (void)testConditionBlock
{
// Creating condition variable
__block BOOL condition = YES;
dispatch_async_if(dispatch_get_main_queue(), condition, ^{
NSLog(#"Block 2 executed");
});
// Canceling the block execution
condition = NO;
// Also, we could use a method to test the condition status
dispatch_async_if(dispatch_get_main_queue(), ![self mustCancelBlockExecution], ^{
NSLog(#"Block 3 executed");
});
}
Try to apply the following code sample to your situation:
__block UIView * tempView = [[UIView alloc] initWithFrame:CGRectMake(50, 100, 220, 30)];
[tempView setBackgroundColor:[UIColor grayColor]];
[self.view addSubview:tempView];
[tempView release];
__block BOOL cancel = NO;
//点击之后就会开始执行这个方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
int i = 0;
while (i < 1000000000 && cancel == NO) {
i++;
}
NSLog(#"Task end: i = %d", i);
//这个不会执行,因为在之前,gcd task已经结束
[tempView removeFromSuperview];
});
//1s 之后执行这个方法
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"A GCD Task Start");
cancel = YES;
[tempView setBackgroundColor:[UIColor blackColor]];
});
I have the following dispatch queue my app :
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^ {
[activeModel freeUpMallocedData];
// UI Updates have to be made on the main thread, so request from GCD.
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^ {
[mainViewController removeTidyUpScreen];
[mainViewController showSceneList];
[activeModel release];
});
});
The freeUpMallocedData method updates a UI Progress View :
- (void) freeUpMallocedData {
// Calculate the percentage increase for each item in the pointerStorageArray as it is released so we can update the Progress Screen.
float arrayCount = [pointerStorageArray count];
float incrementCounter = 1 / arrayCount; // Caculates 1% of pointerStorageArray
float newValue = incrementCounter;
int stepCounter = 0;
NSString * message;
// Now iterate through the pointerStorageArray and free all malloced memory.
for (NSValue * value in pointerStorageArray) {
stepCounter ++;
newValue = newValue + incrementCounter;
message = [NSString stringWithFormat:#"Freeing Up Memory (%d of %d) ...", stepCounter, (int)arrayCount];
free(value);
[self tidyUpProgress:message amount:newValue];
}
}
The tidyUpProgress method then executes on the main thread.
- (void) tidyUpProgress: (NSString *) progressMsg amount: (float) amount {
if (tidyUpMonitorDelegate) {
tidyUpProgressMsg = progressMsg;
tidyUpProgressAmount = amount;
[tidyUpMonitorDelegate performSelectorOnMainThread:#selector(model3DTidyUpProgressUpdate) withObject:nil waitUntilDone:NO];
}
}
- (void) model3DTidyUpProgressUpdate {
progressView.progress = app.activeModel.tidyUpProgressAmount;
loadingStatus.text = app.activeModel.tidyUpProgressMsg;
}
The problem is that the app crashes when the freeUpMallocedData method completes. The reason for this is that my initial dispatch queue moves on to request the main queue which then releases activeView. This seems to hijack the thread from the tidyUpMonitorDelegate before it can perform its last update - when it gets the main thread back the activeView has been released and therefore the app crashes as the model3DTidyUpProgresUpdate method is requesting access to variable in a class which has now been dealloced.
Can anyone advise on how to fix this timing issue ?
Thank you in advance.
Just a thought - try renaming the variable inside the dispatch:
dispatch_queue_t mainqueue = dispatch_get_main_queue();
dispatch_async(mainqueue, ^ {
You use two different mechanisms to schedule tasks in the main thread : dispatch_asyc and performSelectorInMainThread:withObject:waitUntilDone:. Each mechanism uses its own queue, which is read by the main run loop.
The order in which those queue are read is undefined. Thus, a task scheduled by performSelectorInMainThread:withObject:waitUntilDone may be performed after (or before) a task scheduled with dispatch_async regardless of which was scheduled first.
You should update tidyUpProgress: to use dispatch_async. Then the order will be guaranteed.
Moreover, after releasing an object, you should always nullify the variable that holds the reference to that object.
this is wrong way:
float arrayCount = [pointerStorageArray count];
-correct way:
NSUinteger arrayCountInt = [pointerStorageArray count];
NSNumber *arrayCountNumber = [NSNumber numberWithUnsignedInteger:arrayCountInt]
float arrayCount = [arrayCountNumber floatValue];
What if I have a function that returns an int and the return value of the int is taken from the block?
For example:
- (int) queryForKey:(NSString *)aKey view:(UIButton *)aView countView:(UIView *)aCountView counter:(int) count {
//some initialization
[query countObjectsInBackgroundWithBlock:^(int number, NSError * error) {
[aCountView addSubview:self.generateCountLabel];
if (number > 0){
[aView setUserInteractionEnabled:YES];
[aView setEnabled:YES];
}
//return number; //doing this will generate an error
}];
}
also another question is, what if I have an int passed in as an argument of the function above and I would like to assign a value to it. Is some thing like that even possible?
Well your block does not have a return value, it returns void.
To return a value you could use the __block modifier on a variable outside your block and store then answer there which can then be used by the rest of your method (or code).
The problem is that you have a synchronous method (one that wants to return the value immediately) that needs to return a value derived from an asynchronous method (one that goes about it's business in a different thread).
There are a couple of ways of fixing this:
wait for the countObjectsInBackgroundWithBlock: method to complete, use the __block pattern as #simonpie described.
replace the return number; with a call to something somewhere interested in the resulting number. This also means that queryForKey:view:countView: will likely return void.
The latter is the preferred solution as it will not block the thread calling the queryForKey:... method.
Note that you can't diddle UIKit classes on anything but the main thread. If that block is executed on a background thread, then doing what you are doing in the block is invalid.
I've found a better solution. Hopefully this will help someone else who stumbles across the question. I would implement a solution to your code like so:
- (int) queryForKey:(NSString *)aKey view:(UIButton *)aView countView:(UIView *)aCountView counter:(int) count {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
__block int number;
//some initialization
[query countObjectsInBackgroundWithBlock:^(int number, NSError * error) {
dispatch_async(queue, ^{
[aCountView addSubview:self.generateCountLabel];
if (number > 0){
[aView setUserInteractionEnabled:YES];
[aView setEnabled:YES];
}
dispatch_semaphore_signal(sema);
});
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return number; //doing this will no longer generate an error
}
Then encapsulate your call with another dispatch_async so that your semaphore wait call doesn't block the main thread.
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
[self queryForKey:#"AKey" view:myView countView:myCountView counter:aCounter];
});