Array to saved photos always returned as empty - objective-c

I am trying to create an array of all images from the saved photo album that match a certain criteria. Here is a simplified code for it. I add the photos to myImages array and confirmed via the "Added Image" log that the right images get logged. However the array returned by the function is always empty. Fairly new to Objective-C so any suggestions would be helpful.
NSMutableArray * myImages = [NSMutableArray array];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Enumerate just the photos by using ALAssetsGroupSavedPhotos.
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
// Within the group enumeration block, filter to enumerate just photos.
[group setAssetsFilter:[ALAssetsFilter allPhotos]];
[group enumerateAssetsUsingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) {
// The end of the enumeration is signaled by asset == nil.
if (alAsset) {
ALAssetRepresentation *representation = [alAsset defaultRepresentation];
UIImage *latestPhoto = [UIImage imageWithCGImage:[representation fullResolutionImage]];
NSLog(#"Added Image");
[myImages addObject:latestPhoto];
}
}];
}
failureBlock: ^(NSError *error) {
// Typically you should handle an error more gracefully than this.
NSLog(#"No groups");
}];
return myImages;

What is imagesTakenOnDate? Is that supposed to be myImages? If so, you cannot return it in this manner as the block code will execute after the method returns. The method is asynchronous. Rather than "return" you have 2 options to be able to access the modified array outside the function:
option 1: make your method take a completion block as a parameter, and then call the completion block inside the enumerateGroupsWithTypes block, and pass the completion block the array. For example:
typedef void (^CompletionBlock)(id, NSError*);
-(void)myMethodWithCompletionBlock:(CompletionBlock)completionBlock;
then when you're done with success call:
completionBlock(myImages, nil);
and in the failureBlock call:
completionBlock(nil, error);
option 2: make the array an ivar that is retained on your parent object, rather than a local variable, and then declare it as a __block variable so it can be modified within the block.

First thing. Do you really return imagesTakenOnDate? can`t see any reference to this ivar in your code. I would say that your put some breakpoints in your code. In the gdb debugger console you can type:
po myImages
than the debugger will print out the content of your array. Hope that helps

Related

Is a value inside an NSDictionary released when removed?

I have something along the line of this :
#implementation ImageLoader
NSMutableDictionary *_tasks;
- (void) loadImageWithURL:(NSURL *)url callback:(SMLoaderCallback)callback {
NSMutableArray *taskList = [_tasks objectForKey:urlString];
if (taskList == nil) {
taskList = [[NSMutableArray alloc] initWithCapacity:5];
[taskList addObject:callback];
[_tasks setObject:taskList forKey:urlString];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [[UIImage alloc] initWithData:data];
for (SMLoaderCallback cb in taskList) {
cb(image, nil);
}
[_tasks removeObjectForKey:url.absoluteString];
});
});
} else {
[taskList addObject:callback];
}
}
#end
In here, I'm trying to queue up image downloads to gain performance (as an exercise only). So I'm keeping a NSDictionary that maps an URL with an array of callbacks to be called whenever the data is downloaded.
Once the image is downloaded, I no longer need this array, so I remove it from the dictionary.
I would like to know if, in this code (with ARC enabled), my array along with the callbacks are correctly released when I call [_tasks removeObjectForKey:url.absoluteString].
If your project uses ARC and that dictionary is the only thing that is referencing to those values, yes. it will be remove permanently.
ARC keeps track of number of objects that is pointing to some other object and it will remove it as soon as the count reaches 0.
so adding to dictionary -> reference count += 1
removing from dictionary -> reference count -= 1
Somewhe

Get facebook pictures for later use

So, I'm trying to get all the facebook pictures I'm tagged so I can see them on the iPad, but I wanted to make this function so I can call it everytime I would need to get the url's. The problem is, after I call this function, the array is nil, because the values I get are inside a block. How do I make an array to store the data I get for later use?
-(NSArray *)getFacebookTaggedPictures
{
__block NSArray *taggedPictures = [[NSArray alloc]init];
[FBRequestConnection startWithGraphPath:#"me/photos" completionHandler:^(FBRequestConnection *connection, id result, NSError *error)
{
if(!error)
{
taggedPictures = [(NSArray *)[result data]copy];
//NSLog(#"the tagged pictures are: %#",result);
}
}];
return taggedPictures;
}
As you have correctly noticed, the array that you return is empty because your method returns before the response from the API has been received and parsed into taggedPictures inside the completionHandler block.
You must save the array inside that block. I'd recommend changing your method as follows:
-(NSArray *)getFacebookTaggedPicturesWithCompletion:(void (^)(NSArray* photos))completion;
where the calling methods would pass the appropriate completion block to handle the obtained pictures. (i.e. save them to disk, display them, do some processing on them etc.):
void (^loggerBlock)(NSArray* photos) = ^(NSArray *array) {
NSLog(#"obtained photos: %#",[array description]);
//save here instead of logging
};
[self getFacebookTaggedPicturesWithCompletion:loggerBlock];
Hope this helps.

Leaderboard Requests, Nested Blocks, and Retain Cycles

I have developed a leaderboard display class for my iPhone game. The class has the following instance method.
-(void)displayScoresWithRequest:(CBLeaderboard*)request completionHandler:(void(^)())completionHandler
{
if (request_ != nil)
return;
request_ = [[CBLeaderboard alloc] init];
[request_ setCategory:[request category]];
[request_ setPlayerScope:[request playerScope]];
[request_ setTimeScope:[request timeScope]];
[request_ setRange:[request range]];
__block CBLeaderboardDisplay* blockSelf = self;
[request_ loadScoresWithCompletionHandler:^(NSArray* scores, NSError* error)
{
blockSelf->request_ = nil;
NSUInteger scoresCount = [scores count];
if (scoresCount == 0 && error != nil)
return;
NSMutableArray* playerIDs = [NSMutableArray array];
for (GKScore* score in scores)
[playerIDs addObject:[score playerID]];
[GKPlayer loadPlayersForIdentifiers:playerIDs withCompletionHandler:^(NSArray* players, NSError* error)
{
if (scoresCount > [players count] && error != nil)
return;
[blockSelf displayScores:scores players:players];
completionHandler();
}];
}];
[request_ release];
}
As you can see, the method copies a leaderboard request, executes it, and calls the supplied completion handler. A layer in my game calls this method as follows.
-(void)refreshDisplay
{
CBLeaderboard* request = [[CBLeaderboard alloc] init];
[request setCategory:[[sharedGameCenterManager_ classicLeaderboard] category]];
[request setPlayerScope:GKLeaderboardPlayerScopeFriendsOnly];
[request setTimeScope:GKLeaderboardTimeScopeAllTime];
static NSRange kRequestRange = NSMakeRange(1, 3);
[request setRange:kRequestRange];
__block GJGameOver* blockSelf = self;
[display_ displayScoresWithRequest:request completionHandler:^
{
CGSize displayContentSize = [blockSelf->display_ contentSize];
displayContentSize.width = width(blockSelf) - 2.0 * kGJLabelPadding;
[blockSelf->display_ setContentSize:displayContentSize];
CGFloat displayHeight =
bottomEdge(blockSelf->multiplierLabel_) - topEdge(blockSelf->menu_) - 2.0 * kGJLabelPadding;
CGFloat displayScoreDisplaysCount = [blockSelf->display_ scoreDisplaysCount];
CGFloat displayLabelPadding =
(displayHeight - [blockSelf->display_ minContentSize].height) / displayScoreDisplaysCount;
[blockSelf->display_ setLabelPadding:MIN(floor(displayLabelPadding), kGJLabelPadding)];
static CGFloat kFadeInDuration = 2.0;
if ([blockSelf->display_ opacity] == 0)
[blockSelf->display_ runAction:[CCFadeIn actionWithDuration:kFadeInDuration]];
}];
[request release];
}
My game crashes when both the layer and hence display are deallocated and the request has not completed. When the request completes, it attempts to send a message to a deallocated instance and the crash ensues. Is it possible to cancel a leaderboard request? If not, is there any way I can avoid the crash without causing a memory leak?
In both of your blocks, you use __block to allow the block to reference self without retaining it. This is the problem, because you are doing an asynchronous operation, and if the block is executed after self has been deallocated, it is using a dangling pointer. The whole point of blocks retaining objects they capture is to keep them alive so the block can use it.
Not retaining self when making blocks is commonly done to avoid retain cycles. However, I don't see any retain cycles here:
The request_ in displayScoresWithRequest probably retains the block in displayScoresWithRequest
The block in displayScoresWithRequest retains self, the CBLeaderboardDisplay object
The block in displayScoresWithRequest retains the block from refreshDisplay
The block in refreshDisplay retains self, the GJGameOver object
The GJGameOver object retains display_, the CBLeaderboardDisplay object
However, the CBLeaderboardDisplay object does not retain its instance variable request_. (This code is extremely poorly written, as request_ is released at the end of the method but not set to nil. It should probably be made a local variable or something. And a boolean flag should be used if you want to check whether the code has run once or not.)

Get data out of block iOS (Objective-C)

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.)

NSTable loses focus after present:error

I have an NSTableView that lists tags that are stored using Core Data. The default value for a tag is 'untitled' and I need each tag to be unique, so I have a validation routine that traps empty and non-unique values and that works fine. I don't want the user to be able to store the 'untitled' value for a tag, so I am observing the NSControlTextDidEndEditingNotification, which calls the following code:
- (void)textEndedEditing:(NSNotification *)note {
NSString *enteredName = [[[note userInfo] valueForKey:#"NSFieldEditor"] string];
if ([enteredName isEqualToString:defaultTagName]) {
NSString *dString = [NSString stringWithFormat:#"Rejected - Name cannot be default value of '%#'", defaultTagName];
NSString *errDescription = NSLocalizedStringFromTable( dString, #"Tag", #"validation: default name error");
NSString *errRecoverySuggestion = NSLocalizedStringFromTable(#"Make sure you enter a unique value for the new tag.", #"Tag", #"validation: default name error suggestion");
int errCode = TAG_NAME_DEFAULT_VALUE_ERROR_CODE;
NSArray *objArray = [NSArray arrayWithObjects:errDescription, errRecoverySuggestion, nil];
NSArray *keyArray = [NSArray arrayWithObjects:NSLocalizedDescriptionKey, NSLocalizedRecoverySuggestionErrorKey, nil];
NSDictionary *eDict = [NSDictionary dictionaryWithObjects:objArray forKeys:keyArray];
NSError *error = [[NSError alloc] initWithDomain:TAG_ERROR_DOMAIN code:errCode userInfo:eDict];
NSBeep();
[preferencesWindowsController presentError:error];
unsigned long index = [self rowWithDefaultTag];
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:index] byExtendingSelection:NO];
// [self editColumn:0 row:index withEvent:nil select:YES];
}
}
- (unsigned long)rowWithDefaultTag {
__block unsigned long returnInt;
[managedTags enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([[obj valueForKey:#"name"] isEqualToString:defaultTagName]) {
returnInt = idx;
*stop = YES;
}
}];
return returnInt;
}
With the 'editColumn' line commented out, the code works, so if the user accepts the default tag name without editing it, the error is built, displayed and the process finishes by leaving the appropriate row in the table highlighted.
However, I would like to take it that step further and place the user in edit mode. When I uncomment the 'editColumn' line, the behaviour is not at all what I expected - the tableView loses its blue focus box and the row that respresents the new tag is blank. If I click on the tableView, the row becomes visible. I've spent a lot of time on this and have got nowhere, so some help with this would be very much appreciated.
(Note: I tried using textDidEndEditing, which also didn't behave as I expected, but that is a separate issue!)
Answering my own question. Doh!
I already had a method which I used to put the user in edit mode when they clicked the button to add a new tag:
- (void)objectAdded:(NSNotification *)note {
if ([[note object] isEqual:self]) {
[self editColumn:0 row:[self rowWithDefaultTag] withEvent:nil select:YES];
}
}
Creating a notification to call this solves the problem and places the user in edit mode correctly. The important thing is not to try to do this on the existing runloop; so sending the notification as follows postpones delivery until a later runloop:
// OBJECTADDED is a previously defined constant.
NSNotification * note = [NSNotification notificationWithName:OBJECTADDED object:self];
[[NSNotificationQueue defaultQueue] enqueueNotification: note postingStyle: NSPostWhenIdle];
Problem solved. I wasted a lot of time trying to solve this - a classic example of getting too involved in the code and not looking at what I'm trying to do.
I've forgotten where I first saw this posted - whoever you are, thank you!