I have the following requirement:
Given a hierarchical tree-like structure, I am performing a breadth-first-search to walk through the WHOLE dataset. The data is being provided by an API with a method : (makes a request to a server using AFNetworking, saves the result to Core Data and calls back the completion block on success with the stored entries)
-(void) getChildrenForNodeId:(NSNumber*)nodeId
completion:(void (^)(NSArray *nodes))completionBlock;
The method which a controller executes to fetch data:
-(void)getAllNodesWithCompletion:(void (^)(NSArray *nodes))completionBlock{
NSNumber *rootId = ...
[MyNetworkManager getChildrenForNodeId:rootId completion:^(NSArray *nodes){
for(Node *node in nodes){
[self iterateOverNode:node.nodeId];
}
//execute completionBlock with nodes fetched from database that contain all their children until the very last leaf
}];
}
Here is the problem:
-(void)iterateOverNode:(NSNumber*)nodeId {
NSMutableArray *elements = [NSMutableArray array];
[elements addObject:nodeId];
while ([elements count]) {
NSNumber *current = [elements objectAtIndex:0];
[MyNetworkManager getChildrenForNodeWithId:current completion:^(NSArray *nodes) {
/**
In order to continue with the loop the elements array must be updated. This can only happen once we have retrieved the children of the current node.
However since this is in a loop, all of these requests will be sent off at the same time, thus unable to properly keep looping.
*/
for(Node *node in nodes){
[elements addObject:node.nodeId];
}
[elements removeObjectAtIndex:0];
}];
}
}
Basically I need the result of the callback to control the flow of the while loop but I am not sure how to achieve it. My understanding is that the request to getChildrenForNodeWithId:completion: from within the while-loop should happen in a new thread in a SERIAL order so that another should commence after the first one has completed. I am not sure how to achieve this neither with NSOperation nor with GCD. Any help will be greatly appreciated.
What you need here is some recursion. This problem is tricky as we also need a way to track and detect the point at which we have explored every branch to a leaf node.
I'm not a expert with tree search algorithms, so some folks could probably improve on my answer here. Kick this off by calling it with the root node id. self.trackingArray is an NSMutableArray property with __block qualifier. Each time we start a request for a Node, we add it's nodeId into this array, and when it returns, we remove it's nodeId, and add the nodeIds of it's children. We can then know that when count of the tracking array reaches 0, every request made has returned, and has not added child ids to the array. Once you detect we are finished, you could call a saved block or a delegate method.
This solution does not include any error handling. If any request fails, it won't be retried, and all child nodes will be ignored.
- (void)getNodesRecursively:(NSNumber *)nodeId {
// Only do this once
if (self.trackingArray == nil) {
self.trackingArray = [NSMutableArray new];
[self.trackingArray addObject:nodeId];
}
__weak typeof(self) weakSelf = self;
[MyNetworkManager getChildrenForNodeWithId:nodeId completion:^(NSArray *nodes) {
[self.trackingArray removeObject:nodeId];
for (Node *node in nodes) {
[weakSelf.trackingArray addObject:node.nodeId];
[weakSelf getNodesRecursively:node.nodeId];
}
if (weakSelf.trackingArray.count == 0) {
// We are done.
// Reset the state of the trackingArray
self.trackingArray = nil;
// call a method here to notify who ever needs to know.
[weakSelf allNodesComplete];
}
}];
}
Your other methods would look something like this
-(void)getAllNodesWithCompletion:(void (^)(NSArray *nodes))completionBlock{
// Save the block to a property
self.completion = completionBlock;
// Call -getNodesRecursively with root id
[self getNodesRecursively:rootNodeId];
}
Then you could have a second method
- (void)allNodesComplete {
// Call saved block
// Completion should load nodes from core data as needed
self.completion();
}
I haven't tested the code, but does that approach seem sensible? I'm assuming we don't need to capture the here nodes, as they can be loaded from core data as required.
Related
I have an array of PFFiles I retrieve from parse. The PFFiles must be converted to images and I am trying to convert them in a loop, however;
The array of converted images must be in the same order of the array containing the PFFiles.
The problem is, is the loop runs and causes all of the blocks to trigger, then they are all loading at the same time thus the imageArray will result in a different order to the original array of PFFiles, because if one object finishes loading before the previous object, it will be added to the array before the previous one, making it go out of order.
Instead, I would like a loop where it loops through every object in the array, however it doesn't loop onto the next object until the getDataInBackgroundBlock has finished loading and the current object has been added to the new array.
-(void)organizePhotos {
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
[self savePhotos];
}];
}
}
Above is my code. Is there anyway I can make the loop wait until the getDatanBackgroundWithBlock finishes loading?
The problem is, is the loop runs and causes all of the blocks to trigger, then they are all loading at the same time
That is not a problem, it is good - the reason why the call is asynchronous is it can take an arbitrarily long time to complete. If you've got multiple downloads to do then doing then concurrently can be a big win.
thus the imageArray will result in a different order to the original array of PFFiles, because if one object finishes loading before the previous object, it will be added to the array before the previous one, making it go out of order.
This is the problem and can be addressed in a number of ways.
As you are using arrays, here is a simple array based solution: first create yourself an array of the right size and fill it with nulls to indicate the image hasn't yet arrived:
(all code typed directly into answer, treat as pseudo-code and expect some errors)
NSUInteger numberOfImages = pictureArray.length;
NSMutableArray *downloadedImages = [NSMutableArray arrayWithCapacity:numberOfImages];
// You cannot set a specific element if it is past the end of an array
// so pre-fill the array with nulls
NSUInteger count = numberOfImages;
while (count-- > 0)
[downloadedImages addObject:[NSNull null]];
Now you have your pre-filled array just modify your existing loop to write the downloaded image into the correct index:
for (NSUInteger ix = 0; ix < numberOfImages; ix++)
{
PFFile *picture = pictureArray[ix];
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(),
^{ [imageArray replaceObjectAtIndex:ix
withObject:[UIImage imageWithData:data]
});
[self savePhotos];
}];
}
The use of dispatch_async here is to ensure there are not concurrent updates to the array.
If you wish to know when all images have been downloaded you can check for that within the dispatch_async block, e.g. it can increment a counter safely as it is running on the main thread and call a method/issue a notification/invoke a block when all the images have downloaded.
You are also possibly making things harder on yourself by using arrays, and trying to keep items in different arrays related by position. Dictionaries could save you some of the hassle, for example each of your PFFile objects presumably relates to a different URL, and a URL is a perfectly valid key for a dictionary. So you could do the following:
NSMutableDictionary *imageDict = [NSMutableDictionary new];
for (PFFile *picture in pictureArray) {
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(),
^{ [imageDict setObject:[UIImage imageWithData:data] forKey:picture.URL];
};
[self savePhotos];
}];
}
And your can locate the image for a PFFile instance by looking up its URL in the dictionary - which will return nil if the image is not yet loaded.
There are other solutions, and you might want to look into making your code more asynchronous. Whatever you do trying to call asynchronous code synchronously is not a good idea and will impact the user experience.
HTH
Ideally, you should use a synchronous method; however the behavior you are asking for can be achieved using Grand Central Dispatch.
First, create a dispatch_group_t using dispatch_group_create(). Let's call it asyncGroup.
Then, before calling the async method, call dispatch_group_enter(asyncGroup). This increments the counter of the number of calls in the group.
Then, at the end of the async block, call dispatch_group_leave(asyncGroup) to decrement the counter of the number of calls in the group.
Finally, after calling the async method, call dispatch_group_wait(asyncGroup, timeout) to pause thread execution until the group counter reaches zero. Since you increment, make the call, and then wait, the loop will only continue when the async block has been run. Just ensure the timeout is longer than the operation will take.
You can make the code synchronous:
-(void)organizePhotos {
for (PFFile *picture in pictureArray) {
[imageArray addObject:[UIImage imageWithData:[picture getData]]];
[self savePhotos];
}
}
and run it in the background:
[self performSelectorInBackground:#selector(organizePhotos) withObject:nil];
You can use GCD approach which is to use dispatch_group. So, before you start an asynchronous task, call dispatch_group_enter, and then when the asynchronous task finishes, call dispatch_group_leave, and you can then create a dispatch_group_notify which will be called when the asynchronous tasks finish. You can marry this with a completion block pattern (which is a good idea for asynchronous methods, anyway):
Here is similar Question. Perhaps it may also be helpful:
How to wait for method that has completion block (all on main thread)?
You could do that using dispatch_async way, using semaphory, groups, but for your need better practice is using block to get response and do what you need.
Doing that:
-(void)organizePhotos:(void(^)(BOOL responseStatus))status {
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
status(YES);
}];
}
}
So you next at your call you could do that:
__weak typeof(self) weakSelf = self;
[self organizePhotos:^(BOOL responseStatus) {
if(responseStatus){
[weakSelf savePhotos];
}
}];
Or if you dont wan't create extra parameter response to your method you could do like this:
-(void)organizePhotos {
__weak typeof(self) weakSelf = self;
void (^ResponseBlock)(void) = ^{
[weakSelf savePhotos];
};
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
ResponseBlock();
}];
}
}
And another point that you are making wrong when calling "self" inside block, this could lead you to retain cycle and its a bad practice, look at my code how you should do.
I've finished implementing a swipe feature similar to tinder but running into problems when saving objects.
I have 2 columns in the currentUser's row in my DB. One column hold an array of acceptedUsers (users have been liked) and the other is a rejectedUsers column that holds an array of rejected users (users that have been left swiped).
This is how my DB is updated upon swipe:
-(void)cardSwipedLeft:(UIView *)card;
{
NSString *swipedUserId = [[[userBeingSwipedArray objectAtIndex:0] valueForKey:#"user"] valueForKey:#"objectId"];
[currentUserImagesRow addUniqueObject:swipedUserId forKey:#"rejectedUsers"];
[currentUserImagesRow saveInBackground];
This works fine when I left about 2+ seconds between swipes. However fast swiping causes some saves to fail.
Is there a better way to do this without spoiling the users experience of the app?
I've saved multiple rows to my database before using for loops and this has always worked for me. I thought parse.com would be able to handle the speed of the saving.
I'm using both swift and objective-c for this project.
Thanks for your time
Its a fun problem. I think the way to go is to decouple the swiping and the saving a little bit more. Start with a collection of what needs saving...
#property(nonatomic, strong) NSMutableArray *toSave;
#property(nonatomic, assign) BOOL busySaving;
// on swipe
[self.toSave addObject: currentUserImagesRow];
[self doSaves];
- (void)doSaves {
// we get called because of user interaction, and we call ourselves
// recursively when finished. keep state so these requests don't pile up
if (self.busySaving) return;
if (self.toSave.count) {
self.busySaving = YES;
[PFObject saveAllInBackground:self.toSave block:^(BOOL succeeded, NSError *error) {
self.busySaving = NO;
// remove just the elements that were saved, remaining aware that
// new ones might have arrived while the last save was happening
NSMutableArray *removes = [#[] mutableCopy];
for (PFObject *object in self.toSave) {
if (!object.isDirty) [removes addObject:object];
}
[self.toSave removeObjectsInArray:removes];
[self doSaves];
}];
}
}
Now, instead of processing single saves, we can handle small batches. A user swipe causes a single save, and we block additional requests until the current one is complete. During the current request, we let more saves queue up as the user continues to interact. We call ourselves recursively after a save in case one or more records were queued. If none were, the recursive call ends immediately.
EDIT - Saving just one object is easier, just do the same blocking trick and recursive call at the end, but no need to track or save batches...
#property(nonatomic, assign) BOOL busySaving;
// on swipe
[self doSaves];
- (void)doSaves {
if (self.busySaving) return;
if (currentUserImagesRow.isDirty) {
self.busySaving = YES;
[currentUserImagesRow saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
self.busySaving = NO;
[self doSaves];
}];
}
}
Danh led me to my answer. There was an issue on my side which was causing the problem mentioned in the comments above. The same ID was being used after each swipe because I didn't remove it from the array that held my user objects. Below is how I solved the issue and used the Danh's answer to find my solution.
-(void)cardSwipedRight:(UIView *)card
{
NSString *swipedUserId = [[[userBeingSwipedArray objectAtIndex:0] valueForKey:#"user"] valueForKey:#"objectId"];
// I save the swiped users id and the key for the column it will
// be saved in e.g. if liked then "acceptedUsers" if notLiked then
// "rejectedUsers" so that the doSave method saves to correct column
NSArray *array = #[swipedUserId, #"acceptedUsers"];
[self.toSave addObject: array];
//remove user from array since he/she is now saved
[userBeingSwipedArray removeObjectIdenticalTo:[userBeingSwipedArray objectAtIndex:0]];
[self doSaves];
and then:
- (void)doSaves {
if (self.busySaving) return;
if (self.toSave.count) {
self.busySaving = YES;
NSArray *arrayWithSwipedUsersIdAndKeyForColumn = [self.toSave objectAtIndex:0];
[currentUserImagesRow addUniqueObject:[arrayWithSwipedUsersIdAndKeyForColumn objectAtIndex:0] forKey:[arrayWithSwipedUsersIdAndKeyForColumn objectAtIndex:1]];
[currentUserImagesRow saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
self.busySaving = NO;
//remove object that was just saved seeing as it is no longer needed
[self.toSave removeObjectIdenticalTo:arrayWithSwipedUsersIdAndKeyForColumn];
[self doSaves];
}];
}
}
Saving now works 100% of the time providing there is an internet connection. I can swipe as fast as I wish and objects always get saved.
I need to retrieve a couple of NSDicionaries that are compared against an id.
First, I'm calling a NSArray with these id's in them. I'm looping over them to see get the details of that id, and with that i'm calling another pfcloud function. Up until this point, all goes well. However, when I'm logging the payment details of the payment id's, the order sequence is is in a different order than the array I putted it in.
for(__block NSString *paymentId in success){
[self getPaymentDetails:paymentId];
}
So for instance: success = #[#"1",#"2",#"3"]
the method getPaymentDetails will log me#[#"details about 1", #"details about 3", #"details about 2"]
However, I need them to be in the exact same order.
This is my getPaymentDetails code:
-(void)getPaymentDetails:(NSString *)paymentId{
PFUser *currentUser = [PFUser currentUser];
[PFCloud callFunctionInBackground:#"getpaymentdetails"
withParameters:#{#"objectid": paymentId, #"userid": currentUser.objectId}
block:^(NSDictionary *success, NSError *error) {
if(success){
NSDictionary *payment = success;
NSString *amount = [payment objectForKey:#"amount"];
if (![amount isKindOfClass:[NSNull class]]) {
[self.amountArray addObject:amount];
}
else {
[self.amountArray addObject:#""];
}
NSString *from = [payment objectForKey:#"from"];
if (![from isKindOfClass:[NSNull class]]) {
[self.fromArray addObject:from];
}
else {
[self.fromArray addObject:#""];
}
} else{
NSLog(#"Error logged getpaymentdetails: %#", error);
}
}];
}
The values stored in the amountArray for instance, do not match the index of the paymentId
How come and how do I solve this?
It may be simpler to just move the whole for loop into the background and then call the Parse function synchronously
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue, ^{
for(__block NSString *paymentId in success){
[self getPaymentDetails:paymentId];
}
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Release the group when it is no longer needed.
dispatch_release(group);
Then in your getPaymentDetails you would call callFunction:withParameters:error: instead of callFunctionInBackground:withParameters:
This isn't an ideal solution however, as you are eliminating concurrency and so it will take longer to execute.
A better solution is to deal with the fact that the array is unordered at the conclusion of the loop and sort it once all of the data has been retrieved
The request callFunctionInBackground will do is executed asynchronously and there is no guarantee that the first call you make in your loop will finish first. This is not really related to Parse itself, that is just the nature of how this is done. You may end up with the same order by coincidence or a completely random one each time you execute this code.
If you want the order to stay the same, either pass in all IDs to your Cloud Function and update your Cloud Function to handle it or always wait for one call to finish, add the result to your array and then get the details with the next ID (basically a queue).
Currently I am trying to do some async and concurrent tasks, and I am using Azures blob to upload all the images, however the concern is that, for every blob I need to get a SASURL and then upload the images. Also the another side towards it is that I want to have all the operations of the images completed to be uploaded, and hence send a final upload to the database. Although I can send the operation to the database earlier, without having the confirmation of the images completed, but I just wanted to make sure, that the operation does gets completed.
Below is the code for the SASURL block.
- (void)storageServiceBlob:(NSArray*)images
{
StorageService *storageService = [StorageService getInstance];
NSLog(#"%#",[storageService containers]);
NSLog(#"%#",[storageService blobs]);
for (int i = 0; i < [images count]; i++) {
NSString *file_name = [images objectAtIndex:i];
NSString *result = [self imageName:file_name];
NSLog(#"Final: %#", result);
[storageService getSasUrlForNewBlob:result forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
NSLog(#"%#",sasUrl);
[self postBlobWithUrl:sasUrl Image:[images objectAtIndex:i]];
}];
}
}
I want to use gcd in group somehow to determine that after all the completion blocks is called in a group, it executes Post method. Is there anyway to do this in gcd?
There are many ways you could do this. Here's one:
- (void)storageServiceBlob:(NSArray *)imageFilenames
{
StorageService *storageService = [StorageService getInstance];
__block NSMutableSet *remainingImageFilenames = [NSMutableSet setWithArray:imageFilenames];
for (NSString *imageFilename in imageFilenames) {
NSString *imageName = [self imageNameForImageFilename:imageFilename];
[storageService getSasUrlForNewBlob:imageName forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
[self postBlobWithUrl:sasUrl imageFilename:imageFileName];
[remainingImageFilenames removeObject:imageFilename];
if ([remainingImageFilenames count] == 0) {
// you're done, do your thing
}
}];
}
}
A few tips:
Be careful with your naming. There seems to be some ambiguity there.
Generally, idiomatic method name parameters start with a lower-case letter, e.g. myMethodWithThis:andThat:, not MyMethodWithThis:AndThat:.
Fast enumeration, e.g. for (id obj in array) is your friend. Learn and use it.
You can shortcut [array objectAtIndex:1] as array[1].
If you have access to the queue that the requests are going in then you can issue a barrier block.
When you have an async queue a barrier block will sit and wait to be executed until all of the blocks issued before it have run.
If you don't have access to the queue then your best bet is to keep a count.
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.