objective c - for loop to fetch image from iCloud sequentially - objective-c

on IOS, I need to get metadata for a selected set of images. But since the images are backed up to iCloud, sometimes it may immediately return (cached) and sometimes it may take a second or two.
The for loop runs through quickly, I am able to wait for all of the images to be processed before I move forward. But they still are being fetched in parallel. How do I make the for loop run sequentially by waiting for the block to finish before moving on to next image.
// Step 4: Fetch Details like Metadata for this batch
-(void) getDetailsForThisBatchOfNewAssets:(NSMutableArray*) mArrBatchOfNewAssets
withCompletionHandler:(blockReturnsMArrAndMArr) blockReturns{
NSLog(#"%s with arraySize of %lu",__PRETTY_FUNCTION__, (unsigned long)[mArrBatchOfNewAssets count] );
long assetCount = [mArrBatchOfNewAssets count];
NSMutableArray *mArrNewAssetsAndDetails = [[NSMutableArray alloc] init];
NSMutableArray *mArrNewAssetFailed = [[NSMutableArray alloc] init];
if(assetCount == 0){
NSLog(#" Looks like there are no NEW media files on the device.");
return;
}
else
NSLog(#"found %ld assets in all that need to be backed up", assetCount);
dispatch_group_t groupForLoopGetDetails = dispatch_group_create();
for(long i = 0 ; i < assetCount; i++){
PHAsset *currentAsset = [[mArrBatchOfNewAssets objectAtIndex:i] objectForKey:#"asset"];
NSString *mediaIdentifier = [[[currentAsset localIdentifier] componentsSeparatedByString:#"/"] firstObject];
[mArrIdentifiersInThisBatch addObject:mediaIdentifier];
dispatch_group_enter(groupForLoopGetDetails);
[mediaManager getDetailedRecordForAsset:currentAsset
withCompletionHandler:^(NSMutableDictionary *mDicDetailedRecord, NSMutableDictionary *mDicRecordForError)
{
if(mDicRecordForError[#"error"]){
[mArrNewAssetFailed addObject:mDicRecordForError];
NSLog(#"Position %ld - Failed to fetch Asset with LocalIdentifier: %#, adding it to Failed Table. Record: %#",i,[currentAsset localIdentifier], mDicRecordForError);
} else {
[mArrNewAssetsAndDetails addObject:mDicDetailedRecord ];
NSLog(#"Position %ld - added asset with LocalIdentifier to mArrNewAssetsAndDetails %#",i,[currentAsset localIdentifier]);
}
dispatch_group_leave(groupForLoopGetDetails);
}];
} // end of for loop that iterates through each asset.
dispatch_group_notify(groupForLoopGetDetails, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(#"Completed gathering details for this batch of assets for backup. Count : %lu and failed Media count: %lu",(unsigned long)[mArrNewAssetsAndDetails count], (unsigned long)[mArrNewAssetFailed count]);
blockReturns(mArrNewAssetsAndDetails,mArrNewAssetFailed);
});
}
I have looked through several questions on SO on this topic but still have not figured out how to make this run sequentially.
I don't want to do a "self call" for this method, because I'm already doing "self call" at another place before I reach this method and my code is now growing into too many notifications and catches because of that.

Assuming the completion handler of getDetailedRecordForAsset is called on a different thread, you can use a semaphore to block execution (Note: DO NOT DO this on the main thread) inside the loop while waiting for the completion handler.
Remove the dispatch group stuff, then, inside the loop:
create a semaphore right before calling getDetailedRecordForAsset like so: dispatch_semaphore_t semaphore = dispatch_semaphore_create( 0);
as the last statement of the completion handler call dispatch_semaphore_signal( semaphore);
immediately after calling getDetailedRecordForAsset, wait for the end of the completion handler with dispatch_semaphore_wait( semaphore, DISPATCH_TIME_FOREVER);
So the structure of the loop will look like:
for (assets)
{
... // get current asset, media identifier as above
dispatch_semaphore_t semaphore = dispatch_semaphore_create( 0);
[mediaManager getDetailedRecordForAsset:currentAsset
withCompletionHandler:^(NSMutableDictionary *mDicDetailedRecord, NSMutableDictionary *mDicRecordForError)
{
... // handle error or add asset details as above
dispatch_semaphore_signal( semaphore);
}
dispatch_semaphore_wait( semaphore, DISPATCH_TIME_FOREVER);
}

Related

How to stop NSOperationQueue during dispatch_async

I'm adding many block-operations to an operationsqueue in a for loop. In each operation I need to check on another thread if a condition is fulfilled. If the condition is fulfilled, all operations should be cancelled.
I made a sample code to show you my problem:
__block BOOL queueDidCancel = NO;
NSArray *array = [NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
if (queueDidCancel) {return;}
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if ([string isEqualToString:#"1"]) {
queueDidCancel = YES;
[myQueue cancelAllOperations];
}
});
}];
}
Expected output from NSLog:
run: 1
Output I got (it varies between 7 and 9):
run: 1
run: 2
run: 3
run: 4
run: 5
run: 6
run: 7
run: 8
I googled for hours, but I could not find a solution.
I think I found a solution. Here's the updated code:
NSArray *array = [NSArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10", nil];
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
[myQueue addOperationWithBlock:^{
[myQueue setSuspended:YES];
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_main_queue(), ^{
if (![string isEqualToString:#"1"]) {
[myQueue setSuspended:NO];
}
});
}];
}
Let me use more space. You need to sync access to your variable. It is the correct idea but you are using it incorrectly. You need a lock or an atomic ivar or something like that to sync access to it.
Then if you cancel in the dispatch_async bit it happens looooong after all the blocks executed. That is what your output shows. As mentioned in the comment, if you add a NSLog e.g.
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:#"1"]) {
queueDidCancel = YES;
// Add here
NSLog(#"Going to cancel now");
[myQueue cancelAllOperations];
}
you will see what I mean. I expect that to typically execute deep into your array or even after all of the array finished executing.
But the biggest problem is your logic. You need some logic to cancel those blocks. Just messaging cancelAllOperations or setSuspended is not enough and blocks that are already running will keep on running.
Here is a quick example.
NSObject * lock = NSObject.new; // Use this to sync access
__block BOOL queueDidCancel = NO;
NSOperationQueue *myQueue = [NSOperationQueue new];
myQueue.maxConcurrentOperationCount =1;
for (NSString *string in array) {
// Here you also need to add some logic, e.g. as below
// Note the sync access
#synchronized ( lock ) {
if (queueDidCancel) { break; }
}
[myQueue addOperationWithBlock:^{
// You need to sync access to queueDidCancel especially if
// you access it from main and the queue or if you increase
// the concurrent count
// This lock is one way of doing it, there are others
#synchronized ( lock ) {
// Here is your cancel logic! This is fine here
if (queueDidCancel) {return;}
}
NSLog(#"run: %#", string);
dispatch_async(dispatch_get_main_queue(), ^{
if ([string isEqualToString:#"1"]) {
// Again you need to sync this
#synchronized ( lock ) {
queueDidCancel = YES;
}
// This is not needed your logic should take care of it ...
// The problem is that running threads will probably
// keep on running and you need logic to stop them
// [myQueue cancelAllOperations];
}
});
}];
}
Now this example does what yours does but with a bit more locking and a bit more logic and NO cancelAllOperations nor suspended = YESs. This will not do what you want as even with this running threads tend to run to completion and you need logic to stop it.
Also, in this example, I left the exit or cancel condition as is in the main thread. Again here this will probably mean nothing gets cancelled, but in real life you'd typically cancel from some UI e.g. a button click and then you'd do it as here. But you could cancel anywhere using the lock.
EDIT
Based on lots of comments here is another possible way.
Here you check inside the block and based on the check add another block or not.
NSOperationQueue * queue = NSOperationQueue.new;
// Important
queue.maxConcurrentOperationCount = 1;
void ( ^ block ) ( void ) = ^ {
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means continue adding blocks
if ( cond )
{
[queue addOperationWithBlock:block];
}
// else done
} );
};
// Start it all
[queue addOperationWithBlock:block];
Above I use the same block every time which is also quite an assumption but you can change it easily to add different blocks. However, if the blocks are all the same you will only need one and do not need to keep on scheduling new blocks and then can do it as below.
void ( ^ block1 ) ( void ) = ^ {
// Some logic
__block BOOL done = NO;
while ( ! done )
{
// Whatever you have to do ... do it here
xxx
// Perform check
// Note I run it sync and on the main queue, your requirements may differ
dispatch_sync ( dispatch_get_main_queue (), ^ {
// Here the condition is stop or not
// YES means stop! here
done = cond;
} );
}
};

Need clarification on dispatch_group_wait() behavior when dispatch_group_create() and dispatch_group_enter() are called from different queues

I am looking at the Ray Wenderlich tutorial on using dispatch queues to get notified when a group of tasks complete. http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2
The first code shown under "Code that works" is straight from the tutorial. The Alert view(final completion block) get executed after all 3 downloads complete.
I tried to play around with it and moved the dispatch async down in the "Code that does not work" to see what will happen if dispatch_group_create() and dispatch_group_enter() happen on different queues. In this case, the dispatch_group_enter() does not seem to register because the dispatch_group_wait() immediately completes and alert view(final completion block) is executed even before all the downloads have completed.
Can someone explain whats happening in the second case? (This is just for my understanding of how dispatch group works and I realize thats its better to put the entire function in the global concurrent queue to avoid blocking the main thread).
Code that works
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++)
{
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup);
__block Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
NSLog(#"Finished completion block for photo alloc for URL %# and photo is %#",url,photo) ;
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
NSLog(#"Finished adding photo to shared manager for URL %# and photo is %#",url,photo) ;
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
NSLog(#"Executing completion block after download group complete") ;
completionBlock(error);
}
}) ;
}) ;
}
EDITED Code that does not work with extra NSLog statements
Code that does not work
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++)
{
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0),^{
dispatch_group_enter(downloadGroup);
NSLog(#"Enetered group for URL %#",url) ;
__block Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
NSLog(#"Finished completion block for photo alloc for URL %# and photo is %#",url,photo) ;
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
NSLog(#"Finished adding photo to shared manager for URL %# and photo is %#",url,photo) ;
}) ;
}
NSLog(#"Executing wait statement") ;
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
NSLog(#"Executing completion block after download group complete") ;
completionBlock(error);
}
}) ;
}
The "dispatch_group_enter() does not seem to register" because it hasn't actually been called yet by the time that dispatch_group_wait() is called. Or, rather, it's not guaranteed to have been called. There's a race condition.
This isn't specifically about different queues. It's about concurrency and asynchronicity.
dispatch_async() just means "add a task to a list" with an implicit understanding that something, somewhere, somewhen will take tasks off of that list and execute them. It returns to its caller immediately after the task has been put on the list. It does not wait for the task to start running, let alone complete running.
So, your for loop runs very quickly and by the time it exits, it may be that none of the tasks that it has queued have started. Or, if any have started, it may be that they haven't finished entering the group.
Your code may complete its call to dispatch_group_wait() before anything has entered the group.
Usually, you want to be sure that all relevant calls to dispatch_group_enter() have completed before the call to dispatch_group_wait() is made. The easiest way to do that is to have them all happen synchronously in one execution context. That is, don't put calls to dispatch_group_enter() inside blocks that are dispatched asynchronously.

How to use dispatch queue to run function

I'm trying to figure out how to get a database fetch to run in the background. Below are the foreground and background version of the same function. The foreground version works. But in the background version the local variable retval never gets assigned. Putting a breakpoint in the pageInfoForPageKey function tells me that function is never called.
Is self available inside the block?
//foreground version
- (PageInfo*)objectAtIndex:(NSInteger)idx
{
return [[self dataController] pageInfoForPageKey:[[[self pageIDs] objectAtIndex:idx] integerValue]];
}
//background version
- (PageInfo*)objectAtIndex:(NSInteger)idx
{
__block PageInfo* retval = nil;
__block NSInteger pageID = [[[self pageIDs] objectAtIndex:idx] integerValue];
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^{
retval = [[self dataController] pageInfoForPageKey:pageID];
});
return retval;
}
By using dispatch_async, you are telling the system that you want it to run your block some time soon, and that you don't want to wait for your block to finish (or even start) before dispatch_async returns. That is the definition of asynchronous. That is the definition of “in the background”.
The system is doing what you told it to: it is arranging for your block to run, and then it is returning immediately, before the block has run. So the block doesn't set retval before you return retval, because the block hasn't run yet.
If you want to run the database fetch in the background, you need to change your API to pass retval back (to whoever needs it) at a later time, after the block has run. One way is to pass a completion block as a message argument. This is a common pattern for performing fetches in the background. For example, look at +[NSURLConnection sendAsynchronousRequest:queue:completionHandler:].
You might do it like this:
- (void)fetchObjectAtIndex:(NSIndex)idx completion:(void (^)(PageInfo *))block {
block = [block copy]; // unnecessary but harmless if using ARC
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSInteger pageKey = [[[self pageIDs] objectAtIndex:idx] integerValue];
PageInfo* pageInfo = [[self dataController] pageInfoForPageKey:pageKey];
dispatch_async(dispatch_get_main_queue(), ^{
block(pageInfo);
});
});
}
Then you need to change the caller of objectAtIndex: to use this new API instead.

Block in a loop for paging

With the following code I'm trying to read the Instagram API content with paging. In the FOR loop I intent to call the method loopData to get the content with a block which has in every loop a new page id.
...
for (int a = 1; a <= 3; a++)
{
NSLog(#"Loop count: %i", a);
[self loopData];
}
-(void)loopData
{
NSString *next;
next = [Globals sharedGlobalData].nextMaxId;
[client getUserMedia:[userTextField stringValue]
count:kCount
minId:-1
maxId:next
success:^(NSArray *media) {
[textView setString:[media description]];
NSLog(#"Next_Max_Id: %# ", [Globals sharedGlobalData].nextMaxId);
}
failure:^(NSError *error, NSInteger statusCode) {
[self logError:#"media" error:error statusCode:statusCode];
}
];
}
My problem is, that the block runs thee times, but not in every loop cycle. The block runs after the for loop is finished. Therefore the new page id can't passed to the block.
The Log looks like this:
Loop 1
Loop 2
Loop 3
Read content through block
Read content through block
Read content through block
Many thanks for ideas!!
--- Implementation of getUserMedia
// Get a user's media
- (void)getUserMedia:(NSString*)userId // Can be 'self' for the current user
count:(int)count
minId:(int)minId // -1 for start
maxId:(int)maxId // -1 for no upper limit
success:(void (^)(NSArray* media))success
failure:(void (^)(NSError* error, NSInteger statusCode))failure {
// Setup the parameters
NSMutableDictionary* parameters = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInt:count], #"count", nil];
if (minId > 0) [parameters setObject:[NSNumber numberWithInt:minId] forKey:#"minId"];
if (maxId > 0) [parameters setObject:[NSNumber numberWithInt:maxId] forKey:#"maxId"];
// Fire the get request
[self getPath:[NSString stringWithFormat:#"users/%#/media/recent", userId]
modelClass:[InstagramMedia class]
parameters:parameters
collection:success
single:nil
failure:failure];
}
The blocks you pass into this method are executed asynchronously. The client starts an asynchronous network request and immediately returns when you call this method. Once the network request succeeds or fails, one of the blocks you passed to it is invoked. This is pretty much the standard behavior of any API that takes blocks to use for callbacks like this.
I'm not completely sure what you're trying to do inside the blocks with respect to the nextID and whatnot, but you need to know this: 1) The blocks will not run within the loop and 2) they will not run in any guaranteed order. So whatever problem you are trying to solve, you will need to keep that in mind.
Hope this helps, and let me know if you have any questions.

NSThread Timing Problem with GUI

I have a simple thread that runs an infinite loop. Eventually, the loop will check for serial data at a USB port a few thousand times per second, but at the moment, it just writes something to a custom class of NSTextView once every second.
int i;
for (i=1; i>0; i++)
{
[lock lock];
[[self textStorage] replaceCharactersInRange:NSMakeRange([[self textStorage] length], 0) withString:#"test\n"];
[lock unlock];
sleep(1);
}
The issue is that it writes really sporadically. It will do one or two, then wait ten seconds and spit ten of them out at once. If I replace the writing line with an NSLog(#"test"), it logs at nice even intervals. I have another test method in the main thread that accepts input into a text field and puts it into the text view, and doing this seems to update the text view to include the child thread's most recent writes. There shouldn't be anything interfering with it at this point anyway, but I've locked everything everywhere just to be sure. Thanks in advance.
You should always perform operations that affect the UI from the main thread. You can have the child thread create a temporary object that holds the results, and then use performSelectorOnMainThread:withObject:waitUntilDone: to call another method that will do the necessary modifications on the main thread.
NSString * const MDResultKey = #"MDResult";
- (void)someMethod {
//
int i;
for (i=1; i>0; i++) {
// if necessary, create an object to hold results
NSDictionary *results = [NSDictionary
dictionaryWithObjectsAndKeys:#"test", MDResultKey, nil];
[self performSelectorOnMainThread:#selector(updateUIWithResults:)
withObject:results waitUntilDone:NO];
sleep(1);
}
}
- (void)updateUIWithResults:(NSDictionary *)results {
NSString *result = [results objectForKey:MDResultKey];
[lock lock]; // ?
[[self textStorage] replaceCharactersInRange:
NSMakeRange([[self textStorage] length], 0) withString:result];
[lock unlock]; // ?
}
I'd personally be pretty wary of calling anything on an NSTextStorage on a background thread. I think NSTextView reacts to any NSTextStorage changes, and any UI code on a non-main thread is going to have unpredictable problems.
I would just send the new string to the main thread and call -replaceCharactersInRange: there.