I am using an NSOperation subclass (called PointsOperation) to do some calculations in the background in my app. Due to user behaviour, these calculations might need to be canceled, and new calculations started. In that case, I will create a new PointsOperation instance, and add it to the same NSOperationQueue as the first one. As the first thing in the main method of the PointsOperation, it will check whether another operation is already running, and cancel it.
Because the operations are using some shared caches, they cannot be (and don't need to be) running in parallel. Therefore, the second operation will wait until the first one has finished. The resulting code for the main method looks something like this:
static NSOperation *currentOperation = nil;
- (void) main
{
// setting up autorelease pool, catching exceptions, etc
#synchronized(lock) {
if (currentOperation != nil) {
[currentOperation cancel];
[currentOperation waitUntilFinished];
}
currentOperation = self;
}
while (!calculationsFinished && ![self isCancelled]) {
// do calculations
}
currentOperation = nil;
// releasing autorelease pool, etc
}
This all works fine, the first operation gets cancelled, and the second waits for it to finish, and then starts calculating.
The problem is: it takes 3-10 seconds between the first operation ending the main method, and the second one to come out of the waitUntilFinished.
Does anybody have seen this before and knows what to do about that?
I have also tried, instead of the waitUntilFinished, to make the second operation dependent on the first, with "addDependency:" (in the init method, rather than the main). That also works, but has the same problem: the start of the second operation is a number of seconds behind the finish of the first method.
Despite of its name, the cancel method does not magically cancel the operation.
Canceling an operation does not immediately force it to stop what it
is doing. Although respecting the value returned by the isCancelled is
expected of all operations, your code must explicitly check the value
returned by this method and abort as needed.
http://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html
If you did not write your code to check isCancelled property, so the operation's thread will run to the end no matter you cancel it or not.
I tried to reproduce the issue here but my code just work fine with no such delay. This is my code:
#interface PointsOperation : NSOperation {
#private
bool calculationsFinished;
}
#property (nonatomic, assign) int tag;
#end
#implementation PointsOperation
#synthesize tag;
static NSOperation *currentOperation = nil;
static NSString* lock = #"LOCK";
- (void) main
{
NSLog(#"Before autoreleasepool with tag: %d", tag);
#autoreleasepool {
NSLog(#"Before lock");
// setting up autorelease pool, catching exceptions, etc
#synchronized(lock) {
if (currentOperation != nil) {
NSLog(#"Before cancel");
[currentOperation cancel];
NSLog(#"Before waitUntilFinished");
NSDate* beforeWait = [NSDate date];
[currentOperation waitUntilFinished];
NSLog(#"After waitUntilFinished took %f seconds", [[NSDate date] timeIntervalSinceDate:beforeWait]);
}
currentOperation = self;
}
NSLog(#"Before while loop");
int i = 0;
while (!calculationsFinished && ![self isCancelled]) {
// do calculations
[NSThread sleepForTimeInterval:1];
NSLog(#"Inside while loop = %d", i);
calculationsFinished = (++i > 10);
}
NSLog(#"After while loop: i = %d", i);
currentOperation = nil;
// releasing autorelease pool, etc
}
NSLog(#"%#", #"End of method");
}
#end
And here's how I use it:
NSOperationQueue* q = [[NSOperationQueue alloc] init];
q.maxConcurrentOperationCount = 4;
for (int i = 0; i < 10; i++) {
[q addOperation:[PointsOperation new]];
}
The result of time took by waitUntilFinished came in two categories:
After waitUntilFinished took 1.002624 seconds
and
After waitUntilFinished took 0.000749 seconds
which depends on the timing of the call I think.
Maybe you should provide more of your code if possible, as problem might be somewhere else in your code.
Related
Say you have a method that returns information in two separate blocks, like so:
#interface SomeObject : NSObject
- (instancetype)initWithA:(NSString *)aInfo bInfo:(NSString *)bInfo;
#end
- (void)someMethod:(void (^)(NSString *aInfo))firstBlock
secondBlock:(void (^)(NSString *bInfo))secondBlock {
firstBlock(#"a"); secondBlock(#"b");
}
- (void)ourMethod:(void (^)(SomeObject *object))completionBlock {
SomeObject *someObject = [[SomeObject alloc] initWithA:aInfo bInfo:bInfo];
[self someMethod:^(NSString *aInfo) {
//
} secondBlock:^(NSString *bInfo) {
//
}];
completionBlock(someObject);
}
How would you initialize someObject and pass it back when both of the blocks have completed?
Assume that both blocks are executed asynchronously.
I tried fiddling with GCD's dispatch groups to solve this, however, it didn't seem optimal.
Since you need to create your someObject with the values obtained from the two blocks used in the call to someMethod, you need to create someObject after both blocks have been called.
- (void)ourMethod:(void (^)(BOOL initializationComplete))completionBlock {
__block NSString *a = nil;
__block NSString *b = nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
[self someMethod:^(NSString *aInfo) {
a = aInfo;
dispatch_group_leave(group);
} secondBlock:^(NSString *bInfo) {
b = bInfo;
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
SomeObject *someObject = [[SomeObject alloc] initWithA:a bInfo:b];
completionBlock(someObject);
});
}
This doesn't block the caller of ourMethod and it ensures the completion block is only called once both blocks are done.
This solution assumes the two blocks are run asynchronously.
You can use a semaphore, but-- in general-- making an asynchronous operation synchronous is a red flag indicating bad design.
Are the two blocks asynchronous in and of themselves? If so, you could have __block BOOL firstDone = NO; and __block BOOL secondDone = NO; and check appropriately to see if it is time to call the completionBlock. Still ugly and you'll want a synchronization primitive in there to ensure you don't hit a race, but that'd work.
If firstBlock() and secondBlock() are synchronous and on the same queue, then just call completionBlock() after the second is done.
Or, alternatively, if they are asynchronous and simultaneously scheduled, toss 'em on an asynchronous queue and then toss a barrier block on the queue that calls the completionBlock.
The iOS application that I work on has an apple watch app that goes along with it. We recently started getting complaints that the GPS distance updates have slowed down and the watch is a few seconds behind the phone. I have been looking into this and wrote some test code, the reply block from [[WCSession defaultSession] sendMessage:message
replyHandler:replyHandler
errorHandler:errorHandler
is definitely twice as slow in watchOS 2.2 vs 2.1. I have attached the test code below.
#pragma mark - Location Update.
/**
* #description Provides an NSBlockOperation to be executed in an operation queue. This is an attempt to force serial
* processing
*/
- (NSBlockOperation*)distanceUpdateBlock {
NSBlockOperation *blockOp = [[NSBlockOperation alloc] init];
__weak NSBlockOperation * weakOp = blockOp;
__weak typeof(self) weakSelf = self;
[blockOp addExecutionBlock:^{
typeof(weakSelf) blockSafeSelf = weakSelf;
typeof(weakOp) blockSafeOp = weakOp;
if (!blockSafeOp.isCancelled) { // Make sure we haven't already been cancelled.
__block NSDate *startDate = [NSDate date];
__block BOOL completed = NO;
void (^replyBlock)(NSDictionary*) = ^(NSDictionary *message){
if (!blockSafeOp.isCancelled) {
[blockSafeSelf processUserLocationOnWatch:message];
double replyTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSLog(#"Reply Time: %.03f", replyTime);
completed = YES;
}
};
void (^failBlock)(NSError*) = ^(NSError *error) {
if (!blockSafeOp.isCancelled) {
double replyTime = [[NSDate date] timeIntervalSinceDate:startDate];
NSLog(#"Reply Time Fail: %.03f", replyTime);
completed = YES;
}
};
[self fetchUserLocationFromIphoneWithReplyHandler:replyBlock errorHandler:failBlock];
do {
usleep(10000); // 1/100th second wait to throttle evaluations (Don't worry - in final code I will subclass NSOperation and control when it actually finishes - this is for easy testing.)
} while (!completed && !blockSafeOp.isCancelled && [blockSafeSelf isWatchReachable]); //(isWatchReachable just makes sure we have a session with the phone and it is reachable).
}
}];
blockOp.completionBlock = ^{
typeof(weakSelf) blockSafeSelf = weakSelf;
typeof(weakOp) blockSafeOp = weakOp;
if (!blockSafeOp.isCancelled) {
[blockSafeSelf addOperationForLocationUpdate]; // since we are finished - add another operation.
}
};
return blockOp;
}
- (void)addOperationForLocationUpdate {
[distanceUpdateOperationQueue addOperation:[self distanceUpdateBlock]];
}
- (void)startUpdatingLocation {
[self addOperationForLocationUpdate];
}
- (void)stopUpdatingLocation {
[distanceUpdateOperationQueue cancelAllOperations];
}
- (void)fetchUserLocationFromIphoneWithReplyHandler:(nullable void (^)(NSDictionary<NSString *, id> *replyMessage))replyHandler errorHandler:(nullable void (^)(NSError *error))errorHandler {
if (self.isSessionActive) {
NSDictionary *message = #{kWatchSessionMessageTag:kWatchSessionMessageUserLocation};
if (self.isWatchReachable) {
[[WCSession defaultSession] sendMessage:message
replyHandler:replyHandler
errorHandler:errorHandler
];
} else {
errorHandler(nil);
}
} else {
[self activateSession];
errorHandler(nil);
}
}
The handler on the iPhone side simply get's the User location and calls the replyHandler with the encoded information.
The logs for time on 2.2 look like (consistently about a second)
Reply Time: 0.919
Reply Time: 0.952
Reply Time: 0.991
Reply Time: 0.981
Reply Time: 0.963
Same code on 2.1 looks like
Reply Time: 0.424
Reply Time: 0.421
Reply Time: 0.433
Reply Time: 0.419
Also, I've noticed that after 5 mins (300 seconds) the error handlers start getting called on the messages that have already had the reply handler called. I saw another thread where someone mentioned this as well, is anyone else having this happen and know why?
So, Q1 - has anyone run into this performance slow down and figured out how to keep the replyHandler running faster, or found a faster way to get updates?
Q2 - Solution for the errorHandler getting called after 5 mins.
Just a few things to eliminate - I have done my due diligence on testing the iOS code between receiving the message and calling the replyHandler. There is no change in the time to process between iOS 9.2/9.3. I have narrowed it down to this call. In fact, the way we did this in previous versions is now backing up the sendMessage's operationQueue. So now I am forcing a one at a time call with this test code. We don't get backed up anymore, but the individual calls are slow.
So, I was running tests today on the same piece of code, using the same devices, and although the code has not changed, it is now running twice as fast as it initially did (in 2.1). Logs are coming in the range of .12 - .2 seconds. Only thing that has happened since then is software updates. Also, the fail block is no longer called after 5 mins. So both parts of this question are magically working. I am currently using iOS 9.3.4(13G35) and watch is at 2.2.2. Seems to have been an OS issue somewhere in the chain of processing the queue between the watch and the phone. All is good now.
I'm currently trying make a queueHandler that takes an object array as input for executing drive commands on a simple Double robot. I'm currently trying to use GCD in order to serially execute my functions, but when I'm using dispatch_sync on my queue in won't wait until the NSTimer has run its course, but will continue to try and execute the commands from the next object in my array.
I have 3 functions, one which simply initializes an NSMutableArray(loadCommands) with 2 objects and runs the queueHandler, this is called when I toggle a switch. Then the queueHandler reads the variables from the objects(type, timing, queueNr) to determine what type of drive function will be executed and for how long. This I thought could be done in a switch statement, and I figured it would be great if the app could execute the function on the main thread(that is ok!) but it should wait until the NSTimer has run its course. I thought encapsulating the switch case with a dispatch_sync would solve this but it promptly skips to the next iteration in the loop and tries to execute the next function instead, which is drive backwards for 3 seconds.
When I test this with a single object in the array the command will be executed without trouble. I suppose I'm locking up the main thread somehow. Would perhaps waiting for a return value from the function in the #selector in the NSTimer statement help?
I've only played with Objective C for about 10 days, I'd appreciate any help I could get with this bit!
- (void)loadCommands {
//create an objectArray and put 2 objects inside it.
NSMutableArray *driveCommandsArray = [[NSMutableArray alloc] initWithCapacity:4];
//Command 1
DRCommands *C1 = [[DRCommands alloc] init];
C1.timing = 3;
C1.type = 1;
C1.queueNr = 1;
[driveCommandsArray addObject:C1];
//Command 2
DRCommands *C2 = [[DRCommands alloc] init];
C2.timing = 3;
C2.type = 2;
C2.queueNr = 2;
[driveCommandsArray addObject:C2];
//call queueHandler
[self queueHandler:driveCommandsArray];
}
Queue handler:
- (void)queueHandler: (NSMutableArray*) commandArray {
//Now, I'm not sure what I'm doing here, I watched a tutorial that
//solved a vaguely similar problem and he put a dispatch_async before the
//dispatch_sync. I can't run the dispatch_sync clause inside the case
//statement without this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Inside handler!");
unsigned long count;
count = [commandArray count]; //retrieve length/number of objects from the array.
unsigned long a;
for (a = 0; a < count;) {
//run the loop until all objects has been managed.
DRCommands* myObj = (DRCommands*)[commandArray objectAtIndex:a];
//create 2 serial queues.
dispatch_queue_t myQ1;
myQ1 = dispatch_queue_create("myQ1", NULL);
dispatch_queue_t myQ2;
myQ2 = dispatch_queue_create("myQ2", NULL);
int queueID = myObj.queueNr; //retrieve place in queue (not really used yet)
int timeID = myObj.timing; //retrieve the amount of time the command shall be run through the NSTimer
int typeID = myObj.type; //type of command
NSLog(#"Inside for loop!");
if (queueID == a+1) {
a++;
switch (typeID) {
{
case 1:
NSLog(#"inside case 1");
dispatch_sync(myQ1, ^{ //doesn't wait for NSTimer to finish,
//letting the Double drive forward for 3 seconds,
//before resuming operations.
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveForward) userInfo:nil repeats:YES];
});
break;
}
{
case 2:
NSLog(#"inside case 2");
dispatch_sync(myQ2, ^{
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveBackward) userInfo:nil repeats:YES];
});
break;
}
//add more cases
{
default:
break;
}
}
}
NSLog(#"Exited for loop, and count is %lu", a);
}
});
}
Drive commands:
//Go forward X seconds.
- (void)jDriveForward {
shouldDriveForward_ = YES; //sets a condition which is recognized by a callback function to run the device forward.
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveForward_ = NO;
}
}
//Go backwards X seconds.
- (void)jDriveBackward {
shouldDriveBackward_ = YES;
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveBackward_ = NO;
}
}
Provided drive function from the experimental API I'm using
I'm using a "token" such as "shouldDriveForward_" inside the function driveDoubleShouldUpdate which is TRUE for the duration of an NSTimer. I must call my drive methods inside that function for the robot not to default to idle mode. So whenever it is true for X duration, the function for driving forwards or backwards is active.
- (void)doubleDriveShouldUpdate:(DRDouble *)theDouble {
float drive = (driveForwardButton.highlighted) ? kDRDriveDirectionForward : ((driveBackwardButton.highlighted) ? kDRDriveDirectionBackward : kDRDriveDirectionStop);
float turn = (driveRightButton.highlighted) ? 1.0 : ((driveLeftButton.highlighted) ? -1.0 : 0.0);
[theDouble drive:drive turn:turn];
//below are custom functions
//The NSTimer I'm using keep the BOOL values below TRUE for X seconds,
//making the robot go forward/backward through this callback
//method, which I must use
if(shouldDriveForward_ == YES) {
[theDouble variableDrive:(float)1.0 turn:(float)0.0];
}
if(shouldDriveBackward_ == YES) {
[theDouble variableDrive:(float)-1.0 turn:(float)0.0];
}
}
You're kind of jumbled up here with the combination of GCD and NSTimer. There's nothing to say they can't be intermixed, but an all-GCD approach might be easier to get your head around. I think I've discerned the gist of what you're trying to do here, and hacked something together that might be helpful. I've put the whole project up on GitHub, but here's the meat of it:
#import "ViewController.h"
typedef NS_ENUM(NSUInteger, DRCommandType) {
DRCommandUnknown = 0,
DRCommandTypeForward = 1,
DRCommandTypeBackward = 2,
};
#interface DRCommand : NSObject
#property DRCommandType type;
#property NSTimeInterval duration;
#end
#implementation DRCommand
#end
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *commandNameLabel;
#property (weak, nonatomic) IBOutlet UILabel *secondsRemainingLabel;
#property (strong, atomic) DRCommand* currentlyExecutingCommand;
#property (copy, atomic) NSNumber* currentCommandStarted;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do an initial UI update
[self updateUI];
}
- (IBAction)loadCommands:(id)sender
{
DRCommand *C1 = [[DRCommand alloc] init];
C1.duration = 3.0;
C1.type = DRCommandTypeForward;
DRCommand *C2 = [[DRCommand alloc] init];
C2.duration = 3.0;
C2.type = DRCommandTypeBackward;
[self handleCommands: #[ C1, C2 ]];
}
- (void)handleCommands: (NSArray*)commands
{
// For safety... it could be a mutable array that the caller could continue to mutate
commands = [commands copy];
// This queue will do all our actual work
dispatch_queue_t execQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// We'll target the main queue because it simplifies the updating of the UI
dispatch_set_target_queue(execQueue, dispatch_get_main_queue());
// We'll use this queue to serve commands one at a time...
dispatch_queue_t latchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// Have it target the execQueue; Not strictly necessary but codifies the relationship
dispatch_set_target_queue(latchQueue, execQueue);
// This timer will update our UI at 60FPS give or take, on the main thread.
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (1.0/60.0) * NSEC_PER_SEC, (1.0/30.0) * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{ [self updateUI]; });
// Suspend the latch queue until we're ready to go
dispatch_suspend(latchQueue);
// The first thing to do for this command stream is to start UI updates
dispatch_async(latchQueue, ^{ dispatch_resume(timer); });
// Next enqueue each command in the array
for (DRCommand* cmd in commands)
{
dispatch_async(latchQueue, ^{
// Stop the queue from processing other commands.
dispatch_suspend(latchQueue);
// Update the "machine state"
self.currentlyExecutingCommand = cmd;
self.currentCommandStarted = #([NSDate timeIntervalSinceReferenceDate]);
// Set up the event that'll mark the end of the command.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(cmd.duration * NSEC_PER_SEC)), execQueue, ^{
// Clear out the machine state for the next command
self.currentlyExecutingCommand = nil;
self.currentCommandStarted = nil;
// Resume the latch queue so that the next command starts
dispatch_resume(latchQueue);
});
});
}
// After all the commands have finished, add a cleanup block to stop the timer, and
// make sure the UI doesn't have stale text in it.
dispatch_async(latchQueue, ^{
dispatch_source_cancel(timer);
[self updateUI];
});
// Everything is queued up, so start the command queue
dispatch_resume(latchQueue);
}
- (void)updateUI
{
// Make sure we only ever update the UI on the main thread.
if (![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; });
return;
}
DRCommand* currentCmd = self.currentlyExecutingCommand;
switch (currentCmd.type)
{
case DRCommandUnknown:
self.commandNameLabel.text = #"None";
break;
case DRCommandTypeForward:
self.commandNameLabel.text = #"Forward";
break;
case DRCommandTypeBackward:
self.commandNameLabel.text = #"Backward";
break;
}
NSNumber* startTime = self.currentCommandStarted;
if (!startTime || !currentCmd)
{
self.secondsRemainingLabel.text = #"";
}
else
{
const NSTimeInterval startTimeDbl = startTime.doubleValue;
const NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
const NSTimeInterval duration = currentCmd.duration;
const NSTimeInterval remaining = MAX(0, startTimeDbl + duration - currentTime);
self.secondsRemainingLabel.text = [NSString stringWithFormat: #"%1.3g", remaining];
}
}
#end
Let me know in a comment if there's any part you'd like more explanation on.
Note: The other answer here has the command doing a sleep; my approach is fully asynchronous. Which approach is right for you will depend on what your commands are actually doing which wasn't clear from the question.
You only need a single serial dispatch queue to which you will add your tasks.
I would start by defining a task class which implements your various commands - you can subclass if required.
DRCommand.h
#import <Foundation/Foundation.h>
#interface DRCommand : NSObject
#property uint duration;
-(void) dispatch;
#end
DRCommand.m
#import "DRCommand.h"
#implementation DRCommand
-(void)dispatch {
[self startCommand];
sleep(self.duration);
[self stopCommand];
}
-(void) startCommand {
NSLog(#"Override this method to actually do something");
}
-(void) stopCommand {
NSLog(#"Override this method to stop doing something");
}
#end
Then your run queue code will be something like
-(void) runQueue {
DRCommand *c1=[DRCommand new];
c1.duration=5;
DRCommand *c2=[DRCommand new];
c2.duration=7;
DRCommand *c3=[DRCommand new];
c3.duration=3;
NSArray *taskArray=#[c1,c2,c3];
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
for (DRCommand *command in taskArray) {
dispatch_async(queue, ^{
[command dispatch];
});
}
}
Note that you would have subclasses of DRCommand such as DRForwardCommand, DRBackwardCommand and so on, each with appropriate startCommand and stopCommand methods.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 9 years ago.
Improve this question
I have some code which requires the use of blocks. The block fetches a number of data items from a web service, and then possibly needs to fetch more, and then more again after that, then returns all of the data items once it has all required. I'm unsure how to put this into code. Here's an example of what I mean:
NSMutableArray *array = [[NSMutableArray alloc] init];
[webService getLatestItemsWithCount:50 completion:^(NSArray *objects) {
//Some code to deal with these items.
if (moreItemsNeeded == YES) {
//I now need it to loop this block until I'm done
}
}];
How can I get this to work?
EDIT:
Ok, this is what i'm working with - it's the Evernote API. It should be a better example of what I need:
[noteStore findNotesMetadataWithFilter:filter
offset:0
maxNotes:100
resultSpec:resultSpec
success:^(EDAMNotesMetadataList *metadataList) {
for (EDAMNoteMetadata *metadata in metadataList.notes) {
NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];
if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970) {
[array addObject:metadata];
}
else {
arrayComplete = YES;
}
}
//I need it to loop this code, increasing the offset, until the array is complete.
}failure:^(NSError *error) {
NSLog(#"Failure: %#", error);
}];
I prefer to use the fixed-point combinator structure to write block recursion. This way I don't have to mess with __block variables or risk a retain cycle when I forget to set the block to nil at the end of the recursion. All credit for this goes to Mike Ash who shared this code snippet.
Here's my version of his code (which I placed in a globally shared file so I can access this function from anywhere):
// From Mike Ash's recursive block fixed-point-combinator strategy (https://gist.github.com/1254684)
dispatch_block_t recursiveBlockVehicle(void (^block)(dispatch_block_t recurse))
{
// assuming ARC, so no explicit copy
return ^{ block(recursiveBlockVehicle(block)); };
}
typedef void (^OneParameterBlock)(id parameter);
OneParameterBlock recursiveOneParameterBlockVehicle(void (^block)(OneParameterBlock recurse, id parameter))
{
return ^(id parameter){ block(recursiveOneParameterBlockVehicle(block), parameter); };
}
I know this looks super weird and confusing... but it's not too bad once you understand it. Here's what a simple recursive block might look like:
dispatch_block_t run = recursiveBlockVehicle(^(dispatch_block_t recurse)
{
if (! done)
{
// Continue recursion
recurse();
}
else
{
// End of recursion
}
});
run();
When you call recursiveBlockVehicle, you're passing a block that contains your code. recursiveBlockVehicle's job is take this block that you passed and do three things:
Execute the block
Pass the block back through recursiveBlockVehicle and pass that resultant as the parameter to the block
Encapsulate steps 1 and 2 within a simple block and return that
Now, inside your block's code, if you were to call the special recurse block parameter, you're in turn calling your own block all over again (achieving recursion). The nice thing about this strategy is that the memory management is fairly straight-forward. The use of parameters to pass your own code back to yourself reduces the risk of retain cycles. I use this method instead of defining a __block variable of my code because I'm afraid I might forget to set the __block variable to nil at the end of a recursion and result in a nasty retain cycle.
With that in mind, here's how I would implement your function:
OneParameterBlock run = recursiveOneParameterBlockVehicle(^(OneParameterBlock recurse, id parameter)
{
NSNumber *offset = parameter;
[noteStore
findNotesMetadataWithFilter:filter
offset:offset.intValue
maxNotes:100
resultSpec:resultSpec
success:^(EDAMNotesMetadataList *metadataList)
{
for (EDAMNoteMetadata *metadata in metadataList.notes)
{
NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];
if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970)
{
[array addObject:metadata];
}
else
{
arrayComplete = YES;
}
}
//I need it to loop this code, increasing the offset, until the array is complete.
if (! arrayComplete)
{
recurse([NSNumber numberWithInt:offset.intValue + 100]);
}
}
failure:^(NSError *error)
{
NSLog(#"Failure: %#", error);
}];
});
run(#0);
Again, note that you're not calling callback (the block object) inside of the block itself. The reason why is because the block is passing itself as a parameter recurse and executing recurse is how you achieve recursion.
Also, (in case you've actually read this far and wanted to see more), here's a wikipedia page on FPC: http://en.wikipedia.org/wiki/Fixed-point_combinator
Lastly, I have not personally tested the retain cycle issue of a __block variable. However, Rob Mayoff did a fantastic analysis on the issue: https://stackoverflow.com/a/13091475/588253
You should create a variable that references the block to make possible the recursive invocation. It must be noted that at the moment that you assign the block, it is still nil, so if you call it inside the block itself (aka recursively), you'll get a crash while trying to execute a nil block. So the block should have a *__block* storage:
void (^__block myBlock) (NSArray*) = ^(NSArray *objects) {
//Some code to deal with these items.
if (moreItemsNeeded == YES) {
//I now need it to loop this block until I'm done
myBlock(objects);
myBlock= nil; // Avoid retain cycle
}
}];
[webService getLatestItemsWithCount:50 completion: myBlock];
The block in your specific case is "translated" as this one:
void (^__block handler) (EDAMNotesMetadataList)= ^(EDAMNotesMetadataList* metadataList) {
for (EDAMNoteMetadata *metadata in metadataList.notes) {
NSDate *timestamp = [NSDate endateFromEDAMTimestamp:metadata.updated];
if (timestamp.timeIntervalSince1970 > date.timeIntervalSince1970) {
[array addObject:metadata];
}
else {
arrayComplete = YES;
}
}
//I need it to loop this code, increasing the offset, until the array is complete.
if(!arrayComplete)
handler(metadataList);
handler= nil; // Avoid retain cycle
};
Then you can normally call that method passing myBlock as argument.
About retain cycles
To avoid a retain cycle, you should set to nil the pointer to the block when the recursion finishes.
Your code will be simpler to understand and less prone to leaking the block if you don't make the block recursive. Instead, wrap it in a method, and make the block call the method if it needs to keep searching.
This example is based on the code in your question:
- (void)appendNotesMetadataToArray:(NSMutableArray *)array
untilDate:(NSDate *)date withFilter:(EDAMNoteFilter *)filter
offset:(int32_t)offset resultSpec:(EDAMNotesMetadataResultSpec *)resultSpec
{
static const int32_t kBatchSize = 100;
[noteStore findNotesMetadataWithFilter:filter
offset:offset maxNotes:kBatchSize resultSpec:resultSpec
success:^(EDAMNotesMetadataList *metadataList) {
BOOL searchComplete = NO;
for (EDAMNoteMetadata *metadata in metadataList.notes) {
NSDate *timestamp = [NSDate endDateFromEDAMTimestamp:metadata.updated];
if ([timestamp compare:date] == NSOrderedDescending) {
[array addObject:metadata];
} else {
searchComplete = YES;
}
}
if (!searchComplete) {
[self appendNotesMetadataToArray:array untilDate:date
withFilter:filter offset:offset + kBatchSize
resultSpec:resultSpec];
}
} failure:^(NSError *error) {
NSLog(#"Failure: %#", error);
}];
}
With this design, you don't need to declare a reference to the block with an inscrutable type signature, and you don't have to worry about the block leaking because it references itself.
In this design, each call to the method creates a new block. The block references self, and (I assume) self references noteStore, and noteStore references the block, so there is a retain cycle. But when the block finishes executing, noteStore releases the block, breaking the retain cycle.
This is (as far as I've been able to figure out) - sort of an annoying connundrum - and one of blocks' few shortcomings... The following is the go-to archetype I refer to if I REALLY wanna make sure I'm being safe about it..
// declare a "recursive" prototype you will refer to "inside" the block.
id __block (^enumerateAndAdd_recurse)(NSArray*);
// define the block's function - like normal.
id (^enumerateAndAdd) (NSArray*) = ^(NSArray*kids){
id collection = CollectionClass.new;
for (ArrayLike* littleDarling in kids)
[collection add:enumerateAndAdd_recurse(littleDarling)];
return collection;
};
enumerateAndAdd_recurse = enumerateAndAdd; // alias the block "to itself" before calling.
enumerateAndAdd(something); // kicks it all off, yay.
I needed to sync some background threads I got in my iOS app. I needed something to hold the execution of one thread while the other did its job, in order to keep the execution of the code linear.
I've been fighting with this code all day, and after a lot of reading I came up with this code:
-(void)waitOne:(double)timeout
{
if (!shouldRun)
{
shouldRun = true;
double runs = 0;
do {
NSDate* next = [NSDate dateWithTimeIntervalSinceNow:0.5];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:next];
runs++;
} while ((runs/2) < timeout && shouldRun);
}
else
{
#throw [NSException exceptionWithName:#"InvalidHandle" reason:#"The currenct handle is in use" userInfo:nil];
}
}
I can tell you that it got the job done. But I also found some bad feedback at the forum where I found the idea for that algorithm, saying that "it'll make your loop busy wait" and that was a bad idea.
So, I come here to ask. Is there a better way to accomplish what I want?
Look into [NSCondition] which enables you to wait and signal threads
Basically you allocate a NSCondition and in the block you'll have [condition wait]; which will cause the thread to wait. then, when the async operation is done, you call [condition signal]; which will signal the waiting thread to continue.
http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/NSCondition_class/Reference/Reference.html
For future reference, if anyone needs, here's how my code ended up:
#implementation WaitHandle
#synthesize isWaiting;
-(void)waitOne:(double)timeout
{
if (!isWaiting)
{
isWaiting = true;
if (timeout <= 0) {
timeout = 0.1;
}
[handle waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeout]];
}
}
-(void)signal
{
[handle signal];
}
-(id)init
{
self = [super init];
if (self != nil)
{
isWaiting = false;
handle = [[NSCondition alloc] init];
}
return self;
}
-(void)dealloc
{
[self signal];
}
#end