Leaderboard Requests, Nested Blocks, and Retain Cycles - objective-c

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

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

ARC is enabled but having Memory Leak (Objective C)

As you can see, the code below isnt doing much (all commented out) more than enumerating over a set of files, however, my memory usage is growing to over 2 GB after 40 seconds of running the function below which is launched by pressing a button on the UI.
I can run the UI for hours, and before pressing the button, the memory usage does not exceed 8MB.
Given that ARC is turned on, what is holding on to the memory?
removed original code as the edit below made no differance.
EDIT:
Attempted #autoreleasepool{ dispatch_asyny ... } and permutations of that around the while and inside the while loop which had no effect.
Here is the code with autorelasepool added and cleaned up
-(void) search{
self.dict = [[NSMutableDictionary alloc] init];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:#"/tmp/SeaWall.log"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSString *bundleRoot = #"/";
NSFileManager *manager = [NSFileManager defaultManager];
NSDirectoryEnumerator *direnum = [manager enumeratorAtPath:bundleRoot];
NSString *filename;
while ((filename = [NSString stringWithFormat:#"/%#", [direnum nextObject]] ) && !self.exit) {
#autoreleasepool {
NSString *ext = filename.pathExtension;
if ([ext hasSuffix:#"so"] || [ext hasSuffix:#"dylib"] ) {
if (filename == nil || [NSURL URLWithString:filename] == nil) {
continue;
}
NSData *nsData = [NSData dataWithContentsOfFile:filename];
if (nsData != nil){
NSString *str = [nsData MD5];
nsData = nil;
[self writeToLogFile:[NSString stringWithFormat:#"%# - %#", [filename lastPathComponent], str]];
}
}
ext = nil;
} // end autoreleasepool
}
[fileHandle closeFile];
[self ControlButtonAction:nil];
});
}
The memory is not exactly leaked: it is very much ready to be released, but it never has a chance to be.
ARC builds upon the manual memory management rules of Objective-C. The base rule is that "the object/function that calls init owns the new instance", and the owner must release the object when it no longer needs it.
This is a problem for convenience methods that create objects, like [NSData dataWithContentsOfFile:]. The rule means that the NSData class owns the instance, because it called init on it. Once the value will be returned, the class will no longer need the object, and it would need to release it. However, if this happens before the callee gets a chance to retain the instance, it will be gone before anything had a chance to happen.
To solve this problem, Cocoa introduces the autorelease method. This method transfers the ownership of the object to the last autorelease pool that was set up. Autorelease pools are "drained" when you exit their scope.
Cocoa/AppKit/UIKit automatically set up autorelease pools around event handlers, so you generally do not need to worry about that. However, if you have a long-running method, this becomes an issue.
You can declare an autorelease pool using the #autoreleasepool statement:
#autoreleasepool
{
// code here
}
At the closing bracket, the objects collected by the autorelease pool are released (and possibly deallocated, if no one else has a reference to them).
So you would need to wrap the body of your loop in this statement.
Here's an example. This code "leaks" about 10 megabytes every second on my computer, because the execution never leaves the #autoreleasepool scope:
int main(int argc, const char * argv[])
{
#autoreleasepool
{
while (true)
{
NSString* path = [NSString stringWithFormat:#"%s", argv[0]];
[NSData dataWithContentsOfFile:path];
}
}
}
On the other hand, with this, the memory usage stays stable, because execution leaves the #autoreleasepool scope at the end of every loop iteration:
int main(int argc, const char * argv[])
{
while (true)
{
#autoreleasepool
{
NSString* path = [NSString stringWithFormat:#"%s", argv[0]];
[NSData dataWithContentsOfFile:path];
}
}
}
Creating objects in the loop condition is awkward for long loops because these are not picked up by the inner #autoreleasepool. You will need to get these inside the #autoreleasepool scope as well.
Returning
Whenever we return an object (maybe to Swift), we need to register into nearest #autoreleasepool block (by calling autorelease method to prevent memory-leak, according to ownership-rules), but nowadays ARC does that automatically for us;
Whenever ARC disabled; after using alloc and/or init, call autorelease manually, like:
- (NSString *)fullName {
NSString *string = [[[NSString alloc] initWithFormat:#"%# %#",
self.firstName, self.lastName] autorelease];
return string;
}
Memory needs to be released by an autorelease pool.
Otherwise it will be locked up as you are experiencing and it will leak.
In your loop put:
#autoreleasepool { /* BODY */ }

Call block from inside block

In Objective-c we have declared a completion block that acts as a callback for web requests.
If the request fails then I would like to be able to call the function again, from within the completion block. When I do this I get EXC_BAD_ACCESS due to the block not being fully defined.
This is annoying because in Java I can do this with listeners.
Is it possible to call a completion block from within?
Thanks.
The following code throws the error, how could I fix this please?
CustomErrorBlock errorBlock = ^(NSError *error) {
_processing = NO;
loadingView.hidden = YES;
self.infoText.hidden = NO;
self.infoText.text = #"A network connection error has occurred.";
};
CustomCompletionBlock completionBlock = ^(NSData *data){
if(![self processGetTransactions:data withDict:updateDict]) {
[webClient getTransactions:updateDict WithCompletionBlock:completionBlock andErrorBlock:errorBlock];
}
};
[webClient getTransactions:updateDict WithCompletionBlock:completionBlock andErrorBlock:errorBlock];
Should be able to stop the EXC_BAD_ACCESS crash by using __block which tells compiler it must be treated in a special way and also __weak which makes the object not retain strong.
__block CustomErrorBlock errorBlock = ^(NSError *error) {
_processing = NO;
loadingView.hidden = YES;
self.infoText.hidden = NO;
self.infoText.text = #"A network connection error has occurred.";
};
__block CustomCompletionBlock completionBlock = ^(NSData *data) {
if(![self processGetTransactions:data withDict:updateDict]) {
__weak CustomCompletionBlock weakCompletionBlock = completionBlock; // Note seems to always have retain cycle
[webClient getTransactions:updateDict WithCompletionBlock:weakCompletionBlock andErrorBlock:errorBlock];
}
};
[webClient getTransactions:updateDict WithCompletionBlock:completionBlock andErrorBlock:errorBlock];
Note: weak object still displays a message about object being strong for some reason but it sill works without any problems.

Analyzer claiming an object was released when it wasn't

I am getting a static analysis error in this code which doesn't make any sense to me. The error is:
Reference-counted object is used after it is released
This is glue code to allow for PNG loading in a game originally written in C++.
int pngLoad(const char *filename, pngInfo *info, int format, GLuint *textureName)
{
char fullPath[Engine::Settings::MaxPath];
strcpy(fullPath, filename);
appendAppBundlePath(fullPath);
NSString *path = [NSString stringWithCString:fullPath encoding:NSUTF8StringEncoding];
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
UIImage *image = [[UIImage alloc] initWithData:data];
[data release];
Texture2D *loadedTex = [Texture2D alloc];
// ##### Analyzer claims the object is released here: #####
[loadedTex initWithImage:image format:format];
int didLoad;
// ##### Error is here: #####
if (loadedTex.contentSize.width == 0 || loadedTex.contentSize.height == 0)
{
didLoad = 0;
}
else
{
didLoad = 1;
*textureName = loadedTex.name;
// return texture info
info->ScaleFactor = loadedTex.scaleFactor;
info->Width = (float)image.size.width / (float)info->ScaleFactor;
info->Height = (float)image.size.height / (float)info->ScaleFactor;
info->Alpha = 1;
info->PaddedWidth = loadedTex.pixelsWide;
info->PaddedHeight = loadedTex.pixelsHigh;
}
[loadedTex release];
[image release];
return didLoad;
}
If I use Texture2D *loadedTex = [[Texture2D alloc] retain]; this warning is removed, but then an warning that I've leaked an object comes up, so something is seriously weird here.
initWithImage:format: used to contain a [self release] which shouldn't have been there, which I removed when I found this warning. However, even after a full clean and rebuild, I still get the warning. Am I doing something else wrong? Is something not getting properly cleaned up by the Clean command in Xcode?
The analyzer may be right, at least in a general way.
Texture2D *loadedTex = [Texture2D alloc];
[loadedTex initWithImage:image format:format];
In general, "init" might actually discard the object passed in and return a different one. Whether or not this is the case for "Texture2D" is something I don't know, but if the analyzer is going for the general case then it is right.
You should be able to work around that by using
Texture2D *loadedTex = [Texture2D alloc];
loadedTex=[loadedTex initWithImage:image format:format];
Or by simply combining the two calls, as it is done in most Objective-C examples.
You should always combine the alloc and initXXX call when creating objects, in this case
Texture2D *loadedTex = [[Texture2D alloc] initWithImage:image format:format];
An init method need not return the same object it was called with, it is allowed to return a different object.
In this case your result from loadedTex = [Texture2D alloc] would be released and initWithImage would return a different object (which you discard).

Array to saved photos always returned as empty

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