Previous: I am trying to get the number of photos in my camera roll, I started using blocks but have been having some difficulties.
Now: Here is my updated code, I am using async behavior but am returning a value before my block has a chance to complete.
#import "CoverViewController.h"
#import <AssetsLibrary/AssetsLibrary.h>
@interface CoverViewController () <UITextFieldDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
@property(nonatomic, weak) IBOutlet UICollectionView *collectionView;
@property (assign) NSUInteger numberOfPhotos;
@end
@implementation CoverViewController
@synthesize CoverView;
- (void)viewWillAppear:(BOOL)animated
{
NSLog(@"viewWillAppear");
[self beginLoadingPhotoInfo];
}
//Photo collection info
- (void)beginLoadingPhotoInfo {
NSLog(@"loaded beginloadingphotoinfo");
__weak CoverViewController *__self = self;
[self PhotoCount:^(NSUInteger photoCount) {
__self.numberOfPhotos = photoCount;
}];
//[__self.CoverView reloadData];
}
- (void)PhotoCount:(void(^)(NSUInteger))completionBlock {
NSLog(@"photo count start");
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
__block NSInteger result = 0;
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop){
if(group != nil) {
NSLog(@"if");
result += [group numberOfAssets];
NSLog(@"enum: %d", result);
}
else {
NSLog(@"else");
//nil means we are done with enumeration
if (completionBlock) {
completionBlock(result);
}
}
};
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos
usingBlock:assetGroupEnumerator
failureBlock:^(NSError *error) {NSLog(@"Problems");}
];
NSLog(@"result: %u", result);
}
// Layout of collection
//Number of cells in collection
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
NSLog(@"returned number: %d", self.numberOfPhotos);
return self.numberOfPhotos;
}
@end
I've added everything that is relevant in my coverviewcontroller.h file but the code seems to be loading before the block finishes. Here is my attempt to find the error and it shows that photoCount is returning 0 before my block finishes.
2013-07-27 21:16:00.986 slidr[1523:c07] viewWillAppear
2013-07-27 21:16:00.987 slidr[1523:c07] loaded beginloadingphotoinfo
2013-07-27 21:16:00.987 slidr[1523:c07] photo count start
2013-07-27 21:16:00.988 slidr[1523:c07] result: 0
2013-07-27 21:16:00.989 slidr[1523:c07] returned number: 0
2013-07-27 21:16:01.012 slidr[1523:c07] if
2013-07-27 21:16:01.013 slidr[1523:c07] enum: 3
2013-07-27 21:16:01.014 slidr[1523:c07] else
any ideas?
You are mixing and matching synchronous and asynchronous methodologies. There are two possible approaches.
Make the return of your photoCount
method asynchronous by passing a completion block.
- (void)PhotoCount:(void(^)(NSUInteger))completionBlock {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
__block NSInteger result = 0;
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop){
if(group != nil) {
if([[group valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
result += [group numberOfAssets];
}
} else {
// nil group signals we're done with enumeration
if (completionBlock) {
completionBlock(result);
}
}
};
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos
usingBlock:assetGroupEnumerator
failureBlock:^(NSError *error) {NSLog(@"Problems");}
];
}
or
Block the current thread until the operation is complete. This is not usually a good idea in an interactive user app. If you're doing this, you should rethink how this part of the application is structured:
- (NSUInteger)PhotoCount {
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
__block NSInteger result = 0;
dispatch_semaphore_t blockSemaphore = dispatch_semaphore_create(0);
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop){
if(group != nil) {
if([[group valueForProperty:ALAssetPropertyType] isEqualToString:ALAssetTypePhoto]) {
result += [group numberOfAssets];
}
} else {
// nil group signals we're done with enumeration
dispatch_semaphore_signal(blockSemaphore);
}
};
[library enumerateGroupsWithTypes: ALAssetsGroupSavedPhotos
usingBlock:assetGroupEnumerator
failureBlock:^(NSError *error) {NSLog(@"Problems");}
];
dispatch_semaphore_wait(blockSemaphore, DISPATCH_TIME_FOREVER);
NSLog(@"%u", result);
return result;
}
You were also using the resultBlock
variable in a way I didn't understand at all, so I omitted it from my answer.
Just to be clear, I wouldn't go with option 2. It will cause a noticeable delay in the responsiveness of your app, especially if you're calling this on the main thread, and especially if the user has a large asset library.
A great benefit of programming with blocks is that you can defer doing work until you have all the information you need to do the work. Presumably you're going to change some UI element in response to whatever this number turns out to be. Put the code to do that in the completion block that you pass to the method above. Or better yet, factor that code out into a method that you then invoke from the block.
To continue with how you'd use the asynchronous approach, in the collection view controller, you'd want a method that precomputes this number, perhaps when the controller loads:
@property (assign) NSUInteger numberOfPhotos;
- (void)beginLoadingCollectionInformation {
__weak <<<Type of Self>>> *__self = self;
[self PhotoCount:^(NSUInteger photoCount) {
__self.numberOfPhotos = photoCount;
[__self.collectionView reloadData];
}];
}
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return self.numberOfPhotos;
}
If you can manage to get the photo count information into your class before the view appears, that might be ideal. Otherwise, you'll have to insert the content afterwards. It's not ideal, and it's not exactly what I'd do in this case. But this question is starting to stray off into data sources, delegation patterns, and app architecture – well beyond the question of synchronous and asyncronous return values on methods.
Hope that helps.
One last note answering the last question I think you're asking. Change your data reloading call so that it happens within the completion block:
//Photo collection info
- (void)beginLoadingPhotoInfo {
NSLog(@"loaded beginloadingphotoinfo");
__weak CoverViewController *__self = self;
[self PhotoCount:^(NSUInteger photoCount) {
__self.numberOfPhotos = photoCount;
[__self.CoverView reloadData];
}];
}