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.
Related
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);
}
I'm using AFNetworking to fetch remote data, and Magical Record to import and save it locally.
Basically, I'm calling magical record save with a block, within the completion block from the AFNetworking method, which leads to my Unit Tests hanging forever.
Here's the code of the sync method. It is defined in a SyncEngine class, used as a singleton.
NSArray *operations = … ; // An array of AFHTTPRequestOperation defined before
__block BOOL syncSuccess = … ; // Set by operations
__block NSDictionary *syncErrors = … ; // Set by operations
NSArray *batchedOperations =
[AFURLConnectionOperation batchOfRequestOperations:operations
progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
// …
}
completionBlock:^(NSArray *operations) {
NSMutableDictionary *allErrors = [NSMutableDictionary dictionaryWithDictionary:syncErrors];
[_localContext saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error){
if (!success && error)
allErrors[#"MR_saveToPersistentStore"] = error;
syncCompletionBlock(syncSuccess && success, allErrors);
}];
}
];
// _manager is a AFHTTPRequestOperationManager
[_manager.operationQueue addOperations:batchedOperations waitUntilFinished:YES];
So the issue comes from the fact that I'm calling "saveToPersistentStoreWithCompletion" within "completionBlock".
If I use "saveToPersistentStoreAndWait" instead, it works but then I don't get to know if the save was successful or not.
What's the proper way to nest those operations ?
I'm not familiar with AFNetworking 2.0, but won't that code potentially invoke save many times?
Wouldn't it be better to save once after completing all of the batchedOperations?
I think I might have an async problem going on here, which bites cause I thought I had solved it. Anyway, I am making a bunch of web service calls like so:
//get the client data
__block NSArray* arrClientPAs;
[dataManager getJSONData:strWebService withBlock:^(id results, NSError* error) {
if (error) {
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Getting Client Data Error!" message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(#"Okay", nil) otherButtonTitles:nil, nil];
[alert show];
} else {
arrClientPAs = results;
}
}];
and getJSONData is like so:
- (void) getJSONData : (NSString*) strQuery withBlock:(void (^)(id, NSError *))completion {
NSDictionary* dictNetworkStatus = [networkManager checkNetworkConnectivity];
NetworkStatus networkStatus = [[dictNetworkStatus objectForKey:#"Status"] intValue];
if (networkStatus != NotReachable) {
//set up the url for webservice
NSURL* url = [NSURL URLWithString:strQuery];
NSMutableURLRequest* urlRequest = [NSMutableURLRequest requestWithURL:url];
//set up the url connection
__block id results;
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:
^(NSURLResponse* response, NSData* jsonData, NSError* error) {
if (error) {
completion(nil, error);
return;
}
results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error];
completion(results, nil);
}];
} else {
//not connected to a network - data is going to have to come from coredata
}
}
In the first block, if I log arrClientData I can see the data that I am expecting but when I log arrClientData after it it is nil. I was following this SO thread - How to return a BOOL with asynchronous request in a method? (Objective-C) and a couple of others.
Obviously I am trying to get the data after the async call is made. Any help would be appreciated.
The problem lies, I think, in what "asynchronous" means. Here's a diagram:
Step One
__block result;
Step Two - do something asynchonous, including e.g. setting result
Step Three
What order do things happen in here? Step Three happens before Step Two gets finished. That is what asynchronous means: it means, "go right on with this code, don't wait for the asynchronous stuff to finish." So at the time Step Three happens, the result variable has not yet been set to anything.
So, you are just misleading the heck out of yourself with your __block result. __block or no __block, there is no way you are going to find out out what the result is afterwards, because there is no "afterwards". Your code has completed before your __block result is even set. That is why asynchronous code uses a callback (eg. your completion block) which does run afterwards, because it is sequentially part of (appended to) the asynchronous code. You can hand your result downwards through the callback, but you cannot usefully set it upwards from within the block and expect to retrieve it later.
So, your overall structure is like this:
__block NSArray* arrClientPAs; // it's nil
[call getJSONdata] = step one
[call sendAsynchronousRequest]
do the block _asynchronously_ = step two, tries to set arrClientPAs somehow
step three! This happens _before_ step two, ...
... and this entire method ends and is torn down ...
... and arrClientPAs is still nil! 🌻
I repeat: you cannot pass any information UP out of an asynchronous block. You can only go DOWN. You need your asynchronous block to call some method of some independently persistent object to hand it your result and tell it to use that result (and do it carefully, on the main thread, or you will cause havoc). You cannot use any automatic variable for this purpose, such as your declared NSArray variable arrClientPAs; there is no automatic scope any more, the method is over, the automatic variable is gone, there is no more code to run.
Check the value of the 'error 'variable after call:
results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error];
If 'error' isn't nil there is a problem with data which you get in your completion block.
You are mixing styles and confusing the purpose of __block.
Note: When you call a method that will be executed asynchronously you are creating a new execution path which will be executed at some point in the future (which includes immediately) on some thread.
In your getJSONData method you use a __block qualified variable, results, when you should not. The variable is only required within the block and should be declared there:
//set up the url connection
[NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:
^(NSURLResponse* response, NSData* jsonData, NSError* error)
{
if (error) {
completion(nil, error);
return;
}
id results = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves | NSJSONReadingAllowFragments error:&error];
completion(results, nil);
}];
Declaring the variable outside of the block and adding __block just adds pointless complexity. After the call to sendAsynchronousRequest, returns before the request has been performed, the value of results would not be the value assigned in the block. The call to the completion block is performed on a different execution path and probably will not even be executed until after the call to getJSONData has returned.
However what is correct about your getJSONData method is its model - it takes a completion block which sendAsynchronousRequest's own completion handler will call. This is what is incorrect about your call to getJSONData - the completion block you pass does not pass on the results to another block or pass them to some object, but instead assigns them a local variable, arrClientPAs, declared before the call. This is the same situation as described above for getJSONData and will fail for the same reasons - it is not the arrClientPAs fails to "retain the data" but that you are reading it on in the current execution path before another execution path has written any data to it.
You can address this problem the same way getJSONData does - the enclosing method (not included in your question) can take a completion block (code entered directly into answer, expect typos!):
- (void) getTheClientData: ... completionHandler:(void (^)(id))handler
{
...
//get the client data
[dataManager getJSONData:strWebService withBlock:^(id results, NSError* error) {
if (error) {
UIAlertView* alert = [[UIAlertView alloc] initWithTitle:#"Getting Client Data Error!" message:error.description delegate:nil cancelButtonTitle:NSLocalizedString(#"Okay", nil) otherButtonTitles:nil, nil];
[alert show];
} else {
handler(results); // "return" the result to the handler
}
}];
There is another approach. If and only if getClientData is not executing on the main thread and you wish its behaviour to be synchronous and to return the result of the request then you can issue a sendSynchronousRequest:returningResponse:error: instead of an asynchronous one. This will block the thread getClientData is executing on until the request completes.
In general if you have an asynchronous method which you cannot replace by a synchronous one but require synchronous behaviour you can use semaphores to block your current thread until the asynchronous call completes. For an example of how to do this see this answer.
HTH
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I have the following for loop contained within a method:
—(void)_va1idateUsers:(NSArray *)users withCurrentAccount:(ACAccount *)account comp1etionB1ock:(void (“)(TSCSpamUser *user, NSError *error))comp1etionBlock; {
for(TSCSpamUser *userID in users) {
NSString *theID = (NSString*)userID;
NSURL *ur1 = [NSURL URLwith5tring:[NSString stringWithFormat:#"https://api.twitter.com/1/users/show.json?user_id=%#&inc1ude_entities=true", theID]];
SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeTwitter
requestMethod:5LRequestMethodGET
URL:ur1
parameters:nil];
[request setAccount:account];
[request performRequestwithHand1er:*(NSData *responseData, NSHTTPURLResponse *ur1Response, NSError *err0r) {
if ([urlResponse statusCode] == 200) {
TSCSpamUser *user = [[TSCSpamUser a11oc] initwithTwitterID:theID];
user.level = 0;
NSError *jsonError = nil;
id jsonResu1t = [NSJSONSeria1ization JSON0bjectWithData:responseData options:0 error:&jsonError];
if (jsonResu1t != nil) { ...... }
else {
user.level = 0;
}
}
}];
}
}
I need to detect the end of this for loop - but not just the end of the for loop. The completion block is called when the for loop has begun the last iteration, not once it has finished. I need to be sure that everything within the for loop has completed too. How can I do this?
In your case I would iterate through the users' array using an index and then, only for the last index, execute a branch in your completion block, e.g.:
for (NSUInteger index = 0; index < [users count] ; ++index) {
TSCSSpamUser* spamID = [users objectAtIndex:index];
...
[request performRequestWithHandler.... : {
...
if (index == [users count]-1) {
[self loopFullyExecuted];
}
...
}
Where loopFullyExecuted encapsulates what you need to do after your loop is fully done (including completion blocks).
EDIT: if you want that at each iteration in the for loop your program "waits" for the completion block to be fully executed, then the approach needs be completely different.
What you need is defining a method which deals with one userID and where you finally call the performRequest:
-(void) processUserID:(NSUInteger)index {
TSCSSpamUser* spamID = [users objectAtIndex:index];
...
[request performRequestWithHandler.... : {
...
if (index < [users count])
[self processUserID:index+1];
}
...
}
performRequest completion block will start the next iteration, as you can see; so the next element (if any) is processed only after the previous one.
You start the whole process by calling:
[self processUserID:0];
This sounds like a good use case for a dispatch group. Before you start your loop you create a new dispatch group using dispatch_group_create. Inside your loop you enter the group (dispatch_group_enter) for every request you make and inside the completion block for your requests you leave that group (dispatch_group_leave). Right after your loop you call dispatch_group_notify to schedule your completion block that gets called once all your requests are completed.
dispatch_group_t group = dispatch_group_create();
for (TCSpamUser *userID in users) {
dispatch_group_enter( group );
// ...
[request performRequestWithHandler: ^ (...) {
// ...
dispatch_group_leave( group );
}];
}
dispatch_group_notify( group, dispatch_get_main_queue(), ^{
dispatch_release( group );
completionBlock( user, error );
});
Just make sure that for every dispatch_group_enter you call the corresponding dispatch_group_leave.
Using this method you don’t have to worry about the order of the requests getting completed. If you call your completion block in the request handler block for the last user you could end up calling it too early, for example if the second last request takes much longer (which always could happen with asynchronous execution) than the last one.
So,here a simple method with a block
-(void)getPointsInRange:(double)radius nearByPoint:(SGPoint *)nearByPoint
{
SGStorageQuery *query = [SGStorageQuery queryWithPoint:nearByPoint layer:SimpleGeoMainLayerName];
[query setRadius:radius];
[mainClient retain];
[mainClient getRecordsForQuery:query
callback:[SGCallback callbackWithSuccessBlock:
^(id response) {
// you've got records!
// to create an array of SGStoredRecord objects...
NSArray *records = [NSArray arrayWithSGCollection:response type:SGCollectionTypeRecords];
NSLog(#"records received:%i",[records count]);
[self arrayOfPointsReceived:records];
} failureBlock:^(NSError *error) {
// handle failure
NSLog(#"getPointsInRange error:%#",[error description]);
}]];
}
the method connects to some SDK and returns an NSArray with results.
i want to find a way that the getPointsInRange method will return the NSArray.
so its signature will be -(NSArray*)getPointsInRange...
I can do it simply with delegate, but i'd like to do it all within one function.
It seems to me like you want to keep your cake and eat it, too. Or have a method that calls asynchronous code and at the same time returns the results synchronously. You can turn the method into a synchronous one, if that’s what you want:
- (void) computeSomethingAndReturnSynchronously
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self doSomeAsynchronousOperationWithCompletion:^{
// take the call results here
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
}
This will run the asynchronous code and then block the execution until the results from the async call are available. Does that help? (I should add that I would much rather keep the code asynchronous and return the NSArray in another completion block.)