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);
}
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 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.
I have an issue where I see memory usage climbing (but no obvious leaks in Instruments) in my app.
I have a test project with two viewControllers: MainViewController and PhotoViewController. The MainViewController contains a single button that simply pushes PhotoViewController via a uinavigationcontroller pushViewController method.
In PhotoViewController, I am using the ALAssetsLibrary to populate a UITableView with images. I essentially do this in two parts. First, I check to see what assetGroups are available, as I need to show images from the Camera Roll and the Photolibrary. Once that is done, I call another method to enumerate through the actual Assets.
Here is the strange behavior: if I push the PhotoViewController and let it finish the entire enumeration and populate the UITableView, and then pop out back to the MainViewController, everything is fine.
However, if I repeatedly and rapidly push and pop out of the PhotoViewCOntroller (while it hasn't yet finished enumerating and populating the UITableiew),then I see my memory usage gradually climbing until the app finally dies. I don't see any obvious leaks in Instruments.
I don't know the relevant code, but here are two methods that use to enumerate. Of course, in dealloc, I am releasing the relevant ivars.
Is there some way to cancel an enumeration upon pop?
Just as a note, I am basing my test code off this project (https://github.com/elc/ELCImagePickerController), although heavily customized. However, I just tested with that code and the same issue happens. Note that you would only see memory usage climb if you have sufficient ALAssets to enumerate. If there are too few, then it would finish enumerating beforeyou couldpop back out.
Thank you!
- (void)getAssetGroups
{
// Load Albums into assetGroups
dispatch_async(dispatch_get_main_queue(), ^
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Group enumerator Block
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop)
{
if (group == nil)
{
// check what data is available
if([savedPhotosGroup numberOfAssets] > 0 && [libraryGroup numberOfAssets] > 0)
{
// User has both Camera Roll and Photo Library albums
self.tableData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
savedPhotoAssets, NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil),
libraryPhotosAssets, NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil),
nil];
self.sectionKeys = [NSArray arrayWithObjects:NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil), NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil), nil];
}
else if([libraryGroup numberOfAssets] == 0)
{
// User only has Camera Roll
self.tableData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
savedPhotoAssets, NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil),
nil];
self.sectionKeys = [NSArray arrayWithObjects:NSLocalizedString(#"PHOTOPICKER_CAMERAROLL", nil), nil];
}
else
{
//User only has Photo Library
self.tableData = [NSMutableDictionary dictionaryWithObjectsAndKeys:
libraryPhotosAssets, NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil),
nil];
self.sectionKeys = [NSArray arrayWithObjects:NSLocalizedString(#"PHOTOPICKER_PHOTOLIBRARY", nil), nil];
}
NSLog(#"Done enumerating groups");
[self performSelectorInBackground:#selector(enumeratePhotos) withObject:nil];
[self.tview performSelector:#selector(reloadData) withObject:nil afterDelay:1];
return;
}
ALAssetsGroupType groupType = [[group valueForProperty:ALAssetsGroupPropertyType] unsignedIntValue];
if(groupType == ALAssetsGroupSavedPhotos)
{
self.savedPhotosGroup = group;
}
else if(groupType == ALAssetsGroupLibrary)
{
self.libraryGroup = group;
}
};
// Group Enumerator Failure Block
void (^assetGroupEnumberatorFailure)(NSError *) = ^(NSError *error) {
NSLog(#"A problem occured %#", [error description]);
};
// Enumerate Albums
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos | ALAssetsGroupLibrary
usingBlock:assetGroupEnumerator
failureBlock:assetGroupEnumberatorFailure];
NSLog(#"Draining pool");
[pool drain];
});
}
-(void)enumeratePhotos {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"enumerating photos");
[savedPhotosGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result == nil)
{
return;
}
CustomAsset *customAsset = [[[CustomAsset alloc] initWithAsset:result] autorelease];
[customAsset setParent:self];
[savedPhotoAssets addObject:customAsset];
}];
[libraryGroup enumerateAssetsUsingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop)
{
if(result == nil)
{
return;
}
CustomAsset *customAsset = [[[CustomAsset alloc] initWithAsset:result] autorelease];
[customAsset setParent:self];
[libraryPhotosAssets addObject:customAsset];
}];
NSLog(#"done enumerating photos");
[tview performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
// only do this if I want to re-select some assets
if(assetsToRestore)
{
for(NSDictionary *dict in assetsToRestore)
{
NSIndexPath *indexPathToRestore = [dict objectForKey:#"selectedAssetIndexPath"];
int tagToRestore = [[dict objectForKey:#"selectedAssetTag"] intValue];
[self selectAssetWithIndexPath:indexPathToRestore andIndex:tagToRestore];
}
}
[pool drain]; }
correct me if i'm wrong, I thought using autorelease pools was supposed to be done like this now:
#autoreleasepool {
(statements)
}
That worked for me.
Hi i have my class Sensors where i have gps,gyroscope,accelerometer and i want o send data in delegate from nsoperationqueue:
#protocol SensorsDelegate <NSObject>
#optional
- (void)motionManagerDidAccelerateData:(CMAccelerometerData *)accelerometerData;
#end
- (void)startAccelerometr
{
if (motionManager.accelerometerAvailable) {
self.motionManager.accelerometerUpdateInterval = 1.0/10.0;
[self.motionManager startAccelerometerUpdatesToQueue:self.operationQueue
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
if (error) {
[motionManager stopAccelerometerUpdates];
}
else {
[self.delegate motionManagerDidAccelerateData:accelerometerData];
}
}];
}
}
How to send data to delegate which is in main thread ?? Or send acceleremoter data ?
Have you tried using
[self.delegate performSelectorOnMainThread:#selector(motionManagerDidAccelerateData:) withObject:accelerometerData waitUntilDone:NO];
Put onMainThread: method in the same class and change your code like this
- (void)startAccelerometr
{
if (motionManager.accelerometerAvailable) {
self.motionManager.accelerometerUpdateInterval = 1.0/10.0;
[self.motionManager startAccelerometerUpdatesToQueue:self.operationQueue
withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
if (error) {
[motionManager stopAccelerometerUpdates];
}
else {
[self performSelectorOnMainThread:#selector(onMainThread:)
withObject:accelerometerData waitUntilDone:NO];
}
}];
}
}
- (void)onMainThread:(id)accelerometerData{
[self.delegate motionManagerDidAccelerateData:accelerometerData];
}
You can try:
Assuming that delegate is a property of type NSObject<SensorDelegate>* assigned at Sensor init method:
[self.delegate performSelectorOnMainThread:#selector(motionManagerDidAccelerateData:) withObject:accelerometerData waitUntilDone:NO];
Hope it helps.
Change the queue you are sending the accelerometer updates to the main queue.
[self.motionManager startAccelerometerUpdatesToQueue:[NSOperationQueue mainQueue] withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {