I use fast enumeration and in the enumeration block I send network requests asynchronously.
So what happens is the enumerateObjectsUsingBlock: just call the block super fast and let the enumeration block finish after some time.
This leads to different results, because some requests finish faster than other. So it's not sorted as I wanted.
Is there any way to set the block to freeze, and after the asynchronous network requests is completed, to just tell it to go to the next one?
Here is some code
NSArray *sites = [self.fetchedResultsController.fetchedObjects copy];
NSLog(#"sites - %#",sites);
[sites enumerateObjectsUsingBlock:^(Sites *site, NSUInteger idx, BOOL *stop) {
NSLog(#"site name - %#,",site.name);
[[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{
NSLog(#"site name - %#",site.name);
}];
}];
Thanks!
Is there any way to set the block to freeze, and after the
asynchronous network requests is completed, to just tell it to go to
the next one?
NSArray *sites = [self.fetchedResultsController.fetchedObjects copy];
NSLog(#"sites - %#",sites);
[sites enumerateObjectsUsingBlock:^(Sites *site, NSUInteger idx, BOOL *stop) {
NSLog(#"site name - %#,",site.name);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{
NSLog(#"site name - %#",site.name);
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}];
^ Not really the ideal way to do this but if you want to synchronously iterate while waiting for an async request to finish before moving forward, the above will do it via GCD. There are other ways to to where you can iterate and increment a dispatch_group while waiting for all the groups to be left after the async tasks complete such as:
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup);
[self fetchStuffInBackground:background withCompletion:^(NSArray *stuff, NSError *error) {
NSLog(#"leaving stuff");
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchAOtherStuffInBackground:background withCompletion:^(NSArray *stuff, NSError *error) {
NSLog(#"leaving other stuff");
dispatch_group_leave(downloadGroup);
}];
dispatch_group_enter(downloadGroup);
[self fetchLastStuffInBackground:background withCompletion:^(NSArray *lastStuff, NSError *error) {
NSLog(#"leaving last stuff");
dispatch_group_leave(downloadGroup);
}];
}
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
if (callback) {
callback(error);
}
});
I wanted to achieve the same thing but keep using blocks to simplify my code, instead of having the hassle of passing parameters through a recursive method. I came up with this NSArray category:
NS_ASSUME_NONNULL_BEGIN
#interface NSArray(MH)
- (void)mh_asyncEnumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL* stop, dispatch_block_t next))block;
#end
NS_ASSUME_NONNULL_END
#implementation NSArray(MH)
- (void)mh_asyncEnumerateObjectsUsingBlock:(void (^)(id _Nonnull obj, NSUInteger idx, BOOL* stop, dispatch_block_t next))block{
__block NSUInteger index = 0;
__block BOOL stop = NO;
void (^next)();
__block __weak typeof(next) weakNext;
weakNext = next = ^void() {
void (^strongNext)() = weakNext;
// check if finished
if(stop || index == self.count){
return;
}
id obj = self[index];
index++;
block(obj, index - 1, &stop, strongNext);
};
next();
}
#end
It's used like this:
NSArray* a = #[#"Malc", #"Bob", #"Jim"];
[a mh_asyncEnumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL *stop, dispatch_block_t next) {
// simulate an async network call
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"%#", obj);
next();
});
}];
Output:
2016-01-04 22:41:04.631 Malc
2016-01-04 22:41:05.632 Bob
2016-01-04 22:41:06.720 Jim
As you can see, this demo outputs each string in the array with 1 second delay. You can use it by doing your network call inside the block and then calling next when it's done. If you hit an error and would like to cancel just set *stop = YES; before calling next(), the same as you would do with normal enumeration.
NSArray *sites = [self.fetchedResultsController.fetchedObjects copy];
NSLog(#"sites - %#",sites);
[sites mh_asyncEnumerateObjectsUsingBlock:^(Site *site, NSUInteger idx, BOOL *stop, dispatch_block_t next){
NSLog(#"site name - %#,",site.name);
[[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{
if(error){ // your completion block should have an error param!!!
*stop = YES;
}
NSLog(#"site name - %#",site.name);
next();
}];
}];
Is there any way to set the block to freeze, and after the asynchronous network requests is completed, to just tell it to go to the next one?
You can achieve that result by reorganizing your code: instead of using enumeration, just execute the async request from the completion block, one at the time:
- (void) doRequestAsync:(NSArray*)sites index:(NSUInteger)index {
if (index >= [sites count]) return;
NSString* site = [sites objectAtIndex:index];
[[Wrapper sharedWrapper] sendRequestTo:site completionBlock:{
NSLog(#"site name - %#",site.name);
[self doRequestAsync:sites index:++index];
}];
}
Alternatives to this are modifying your Wrapper class so that it uses async networking (but use it on a secondary thread, then, to avoid blocking the UI).
Or you might implement the Async Completion Token pattern so to be able to reorder the responses when they are received.
Related
I am trying to pull the turn based games in which a player is participating in order to populate my tableView.
This is my function to pull their games:
- (void) loadMatchDataWithArray:(NSMutableArray*)currentGames Flag:(bool*)returned
{
NSMutableArray* __block blockGames = currentGames;
bool* __block blockReturn = returned;
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
if (matches)
{
for (int i = 0; i < matches.count; i++)
{
[(GKTurnBasedMatch*)matches[i] loadMatchDataWithCompletionHandler: ^(NSData *matchData, NSError *error)
{
int size = [matchData length];
if (size != 0)
{
Game* game = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
[blockGames addObject:game];
}
else
{
Game* game = [[Game alloc] init];
[blockGames addObject:game];
game.activePlayer = [GKLocalPlayer localPlayer];
}
*blockReturn = true;
}];
}
}
else
{
*blockReturn = true;
}
}];
}
And this is where I call it:
- (void)viewDidLoad
{
[super viewDidLoad];
[[self tableView]
setBackgroundView:[[UIImageView alloc]
initWithImage:[UIImage imageNamed:#"iPhoneBackground-568h"]]];
bool* returned = false;
[[GKMatchHelper sharedInstance] loadMatchDataWithArray:currentGames Flag:returned];
while (!returned);
[self.tableView reloadData];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
Sadly, this is just giving me a blank black screen and never returns. Is there a way that I can detect when my block comes back and display a loading spinner until then, at which point I would reload the table?
EDIT:
I have revised my code and brought the function inside my MainMenuViewController, and now it builds but never displays the data.
- (void) loadMatchData
{
NSMutableArray* __block blockGames = currentGames;
MainMenuViewController* __weakSelf = self;
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
if (matches)
{
for (int i = 0; i < matches.count; i++)
{
[(GKTurnBasedMatch*)matches[i] loadMatchDataWithCompletionHandler: ^(NSData *matchData, NSError *error)
{
int size = [matchData length];
if (size != 0)
{
Game* game = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
[blockGames addObject:game];
}
else
{
Game* game = [[Game alloc] init];
[blockGames addObject:game];
game.activePlayer = [GKLocalPlayer localPlayer];
}
[__weakSelf.tableView reloadData];
}];
}
}
[__weakSelf.tableView reloadData];
}];
[__weakSelf.tableView reloadData];
}
And now in my ViewDidLoad I just call:
[self loadMatchData];
Oh dear. Do NOT halt the program execution with "while" loops!
Why not simply call [self.tableView reloadData] at the end of your block?
So,
Remove the last 2 lines in the viewDidLoad method
Replace *blockReturn = true; with [self.tableView reloadData] (you might need to keep a weak reference to 'self' to avoid retain cycles)
Never ever use while (this and that) to wait for an operation to complete. A non-responsive UI is bad and it will cause the users to abandon your app.
I am using the MKNetworkKit library to make REST calls to a server. This particular snippet sets is called when a user wants to make a photo favorite or not favorite. It is pretty straight forward objective-c block code if we are only setting favorite flag for a single photo. However I have impelmented a multiselect mechanism in the GUI so that a user can select several photos and favorite them all at once.
The function I wrote to do this works just fine, but it just doesn't feel very clean to me.
What I dislike:
Tracking all of the callbacks with a block counter. I am wondering if there is a more elegant way to handle this.
The same code exists in both completion blocks. However that is just how MKNetworkKit is used (one block for success, one for error). I suppose if I made this an instance method, I could handle it by calling another i-method, but that seems just as messy to do all the setup. I'd like to keep this as a handy class method utility.
Suggestions?
+(BOOL)updateAssets:(NSArray*)assets
isFavorite:(BOOL)isFavorite
completion:(MyAssetCompletion)completion // (BOOL success)
{
assert(assets);
if (assets == nil || assets.count == 0) return NO;
__block BOOL bError = NO;
__block NSInteger counter = 0; // Use a counter to track number of completed REST calls
for(MyAsset *asset in assets){
MyUpdateAssetForm *form = [[MyUpdateAssetForm alloc] init];
form.isPrivate = isFavorite ? #(1) : #(0);
[[MyRESTEngine sharedInstance] updateAssetWithUUID:asset.UUID
withForm:form
completionBlock:^{
counter++;
if(counter == assets.count){
completion(bError == NO);
}
} errorBlock:^(NSError *error, NSString *additionalInfo) {
bError = bError || YES;
counter++;
if(counter == assets.count){
completion(bError == NO);
}
}];
}
return YES;
}
You can try this (untested):
(By the way, your current implementation is not thread safe as the counter might be incremented from more then one thread in a non-atomic manner)
#see NSConditionLock
- (BOOL) updateAssets:(NSArray*)assets
isFavorite:(BOOL)isFavorite
completion:(bool_block_t)completion
{
assert(assets && [assets count]);
__block NSMutableArray* failed = [NSMutableArray new];
__block NSConditionLock* lock = [[NSConditionLock alloc] initWithCondition:[assets count]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
[lock lockWhenCondition:0];
completion([failed count] == 0);
});
NSNumber* val = (isFavorite ? #1 : #0);
for (MyAsset* asset in assets) {
MyUpdateAssetForm* form = [[MyUpdateAssetForm alloc] init];
form.isPrivate = val;
[self updateAssetWithUUID:asset.UUID
withForm:form
completionBlock:^{[lock unlockWithCondition:[lock condition] - 1];}
errorBlock:^(NSError *error, NSString *additionalInfo)
{
[lock lock];
[failed addObject:asset];
[lock unlockWithCondition:[lock condition] - 1];
}];
}
return YES;
}
I'm trying to pick out some camera roll meta data. When I enumerate through the assets, I cannot seem to retrieve any information and get an empty array. Is there a step I'm missing?
My code:
assets = [[NSMutableArray array] init];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(asset != NULL) {
[assets addObject:asset];
dispatch_async(dispatch_get_main_queue(), ^{
});
}
};
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[group enumerateAssetsUsingBlock:assetEnumerator];
}
};
library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failed.");
}];
NSLog(#"%#", assets); //prints an empty array
Midhun MP is right that you are not waiting for the asynchronous enumeration to complete. In this case, you have asynchronous blocks calling other asynchronous blocks, so it is not simple to know when all enumeration is done.
If you would like to know when this is done, and end up with an array that contains all of the enumerated assets, you could use dispatch_groups. Here is one way you could do that (I've included multiple ALAssetGroup types to show that this can work with multiple albums):
dispatch_group_t loadingGroup = dispatch_group_create();
NSMutableArray * assets = [[NSMutableArray array] init];
NSMutableArray * albums = [[NSMutableArray array] init];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(index != NSNotFound) {
[assets addObject:asset];
dispatch_async(dispatch_get_main_queue(), ^{ });
} else {
dispatch_group_leave(loadingGroup);
}
};
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[albums addObject: group];
} else {
NSLog(#"Found %d albums", [albums count]);
// album loading is done
for (ALAssetsGroup * album in albums) {
dispatch_group_enter(loadingGroup);
[album enumerateAssetsUsingBlock: assetEnumerator];
}
dispatch_group_notify(loadingGroup, dispatch_get_main_queue(), ^{
NSLog(#"DONE: ALAsset array contains %d elements", [assets count]);
});
}
};
ALAssetsLibrary * library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos | ALAssetsGroupAlbum
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failed.");
}];
(In this example, it is safe to have various blocks adding to assets and albums because the enumeration is all happening on the main thread.)
If you are running this on iOS 6, it may be that the user has denied access for you app to access the asset library. If this is the case, the failureBlock is called.
Also note that the usingBlock is called asynchronously so your attempt to log assets is premature. You should move the NSLog statement into the end of the enumeration block.
From the docs for enumerateGroupsWithTypes:usingBlock:failureBlock:
The results are passed one by one to the caller by executing the enumeration block.
This method is asynchronous. When groups are enumerated, the user may be asked to confirm the application's access to the data; the method, though, returns immediately. You should perform whatever work you want with the assets in enumerationBlock.
If the user denies access to the application, or if no application is allowed to access the data, the failureBlock is called.
Your NSLog will always display an empty array because the NSLog statement will work before completing the asynchronous enumeration block.
Solution:
First check that your photolibrary is not empty.
Then add NSLog in:
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
if(asset != NULL) {
[assets addObject:asset];
NSLog(#"Asset : %#", asset);
dispatch_async(dispatch_get_main_queue(), ^{
});
}
};
What is equivalent of Win32's WaitForMultipleObjects function in iOS?
This is roughly what I want:
NSCondition *condition1;
NSCondition *condition2;
NSCondition *condition3;
wait_for_conditions([NSArray arrayWithObjects: condition1, condition2, condition3, nil],
^{
// Some code which must be executed when all conditions were fired
});
// in some other places of program:
[condition1 signal];
// ...
[condition2 signal];
// ...
[condition3 signal];
What are the ways to accomplish this in iOS?
Edit: I'm not bound to usage of NSCondition, any other synchronization things will be ok (I've just googled and found NSCondition).
You can create a NSNotifications for each condition with unique notification name.
Then for each notification will call the same function.
What about this?
void async(void (^block)())
{
[NSThread detachNewThreadSelector:#selector(invoke) toTarget:[block copy] withObject:nil];
}
__attribute__((sentinel(NULL)))
void wait_for_conditions(void (^block)(), NSCondition *condition, ...)
{
va_list args;
va_start(args, condition);
NSMutableArray *conditions = [NSMutableArray array];
do {
[conditions addObject:condition];
} while ((condition = va_arg(args, NSCondition *)));
va_end(args);
NSCondition *overallCondition = [NSCondition new];
for (int i = 0; i < conditions.count; i++) {
NSCondition *cond = [conditions objectAtIndex:i];
async(^{
[cond lock];
[cond wait];
[cond unlock];
[overallCondition lock];
[overallCondition signal];
[overallCondition unlock];
});
}
for (int i = 0; i < conditions.count; i++) {
[overallCondition lock];
[overallCondition wait];
[overallCondition unlock];
}
if (block)
block();
}
Obviously this has the drawback of effectively doubling your threads, but I don't know if there is an easier way of accomplishing this.
OK, I ended up using my own solution using GCD's groups of blocks notifications. I've also used serial queue (instead of concurrent) which guarantees we create only 1 additional thread.
#interface WaitCondition : NSObject
- (void) fire;
#end
#implementation WaitCondition {
BOOL fired;
NSCondition *condition;
}
- (id) init
{
if ((self = [super init])) {
condition = [NSCondition new];
}
return self;
}
- (void) fire
{
[condition lock];
[condition signal];
fired = YES;
[condition unlock];
}
- (void) wait
{
[condition lock];
while (!fired) {
[condition wait];
}
[condition unlock];
}
#end
void Dispatch_NotifyForConditions(NSArray *conditions, dispatch_block_t completion)
{
dispatch_queue_t queue = dispatch_queue_create("notify_queue", NULL);
dispatch_group_t group = dispatch_group_create();
for (WaitCondition *condition in conditions)
{
NSCAssert([condition isKindOfClass:[WaitCondition class]], #"Dispatch_NotifyForConditions: conditions must contain WaitCondition objects only.");
dispatch_group_async(group, queue, ^{
[condition wait];
});
}
dispatch_group_notify(group, queue, completion);
dispatch_release(queue);
dispatch_release(group);
}
I am facing an issue in updating to core data using batch updating on a background thread. In the below code I am using main thread to notify user with a progress view and a string which calls a method in appdelegate. But if I am getting bad access error in the NSEntity line in random number of data where I have thousands of objects to update.if I uncomment the NSLOG i have indicated below there is no error, or if i comment the main thread no error, or if I dont update through batch instead if I use bulk update then also no error. IF i comment autorelease pool also the error is appearing. Will some body help me on this please.
Thanks in advance,
Cheers,
Shravan
NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
NSUInteger iterator = 1;
for (int i = 0; i < totalNo; i++) {
NSDictionary *alertResult = [[alertResultList objectAtIndex:i] retain];
if (alertResult == nil) {
continue;
}
//managedObjectContext = [appDelegate.managedObjectContext retain];
NSLog(#"Object Count:%u", [[managedObjectContext insertedObjects]count]);
AlertResult *result = (AlertResult *)[NSEntityDescription
insertNewObjectForEntityForName:#"AlertResult"
inManagedObjectContext:managedObjectContext];
[result setUserName:#"A"];
iterator++;
//When count reaches max update count we are saving and draining the pool and resetting the pool
if (iterator == kUploadCount) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
[tempPool drain];
tempPool = [[NSAutoreleasePool alloc] init];
iterator = 0;
}
//Adding code to change the display string for the lock view to notify user
float count1 = (float)(counter/totalAlerts);
counter = counter + 1.0f;
NSString *dispStr = [NSString stringWithFormat:#"%f",count1];//[NSString stringWithFormat:#"Loading %d out of %d alerts",(i+1),totalAlerts];
NSString *dispMess = [NSString stringWithFormat:#"Alerts %d of %d",(i+1),totalNo];
[self performSelectorOnMainThread:#selector(changeLockScreenMessageWith:) withObject:[NSArray arrayWithObjects:dispStr,dispMess, nil] waitUntilDone:YES];
//NSLog(#"count"); /* If I uncomment this line code runs fine */
[alertResult release];
alertResult = nil;
}
//If count is inbetween the update limit we are updating and we are draining the pool
if (iterator != 0) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
//[tempPool drain];
}
//Out side the previous method
- (BOOL)update {
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"%#", [error userInfo]);
return NO;
}
return YES;
}
The most likely cause of the kind of crash you're describing is using your managedObjectContext across threads. managedObjectContext is not thread-safe. You must create a new MOC for each thread. I assume managedObjectContext is an ivar; you should never access your ivars directly like this (except in init and dealloc). Always use an accessor to handle memory management for you.
The reason NSLog makes it crash is because NSLog dramatically changes the timing of this function, and you have a race condition.