(NSError *__strong *)magic - objective-c

Without any preamble I want to show you problem I have in my program, I commented out steps and my thoughts for that steps. (I didn't include #interface part for shortness, it has same method with the same signature as in #implementation)
#implementation Dummy
- (int)testing:(NSError *__strong *)error
{
*error = [[NSError alloc] initWithDomain:#"hello" code:42 userInfo:nil];
// 3. retain count = 1
// 4. because of ARC 'error' object was released for this time
// (assembly output is my proof) object is deallocated
// retain count = 0
return 0;
}
#end
int main()
{
NSError *e = nil; // 1. retain count = 0 (obviously)
Dummy *dummy = [[Dummy alloc] init];
[dummy testing:&e]; // 2. passing reference to an error object
// 'e' for this time has to be just a trash, or nil maybe,
// but next log gives me correct output:
NSLog(#"%# %li", [e domain], [e code]); // 'hello 42'
return 0;
}
How does an error object exist after it death? I understand that using NSError *__autoreleasing * will be right way to go, and situation will be trivial in that case, but how compiler reasoning for this code, where is my mistake in judgements?
It's a bit an artificial question, but I can't throw out this situation from my head, I think I'm loosing something.
Here is part of disassembly for -[Dummy testing:]
callq 0x100000e8c <dyld_stub_objc_msgSend>
mov -0x18(%rbp),%rcx
mov (%rcx),%rdx
mov %rax,(%rcx)
mov %rdx,%rdi
callq 0x100000e92 <dyld_stub_objc_release>
mov -0x24(%rbp),%eax
add $0x40,%rsp
pop %rbp
retq
If I understood correctly, there is only one object in this method, and it's clearly released, not autoreleased or something else.

I suspect your confused about what's getting released. I just checked the assembly output, and there is a call to objc_release(), though I'm not familiar enough with x86 assembly to actually trace precisely what's going on. However, I do know that the code here is expected to emit something of the equivalent of:
NSError *temp = [[NSError alloc] initWithDomain:#"hello" code:42 userInfo:nil];
[*error release];
*error = [temp retain];
[temp release];
and of course the optimizer will shrink that to
NSError *temp = ...
[*error release];
*error = temp;
So I think you're seeing the call to objc_release() and thinking that your newly-allocated error is being released. It's not. The previous value of *error is being released before the newly-allocated error is placed in that location.

Related

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 */ }

Casting a CFErrorRef to an NSError (or opposite) with ARC

I used to cast an NSError to CFErrorRef like this and using it in SMJobBless
NSError *error
BOOL removed = SMJobRemove(kSMDomainSystemLaunchd,
(CFStringRef) daemonBundleID,
auth,
true,
(CFErrorRef*) &error);
if (!removed) {
NSLog(#"Failed to remove existing PacketTool");
[NSApp presentError: error];
}
As I've had errors with ARC, "Cast of an indirect pointer to an Obj-C pointer to 'CFErrorRef' is disallowed with ARC", I changed and decided to do the opposite
CFErrorRef *cfError = nil;
BOOL blessed = SMJobBless(kSMDomainSystemLaunchd, (__bridge CFStringRef)daemonBundleID,
auth,
cfError);
if (!blessed) {
NSError *error = (__bridge NSError *)cfError;
NSLog(#"Failed to bless PacketTool: %#", error);
[NSApp presentError: error];
return FALSE;
}
Now I've got an "Incompatible types casting 'CFErrorRef' to NSError *" with __bridge cast
What can I do?
Update: Thanks to Greg, correct code is now:
CFErrorRef cfError = nil;
BOOL blessed = SMJobBless(kSMDomainSystemLaunchd,
(__bridge CFStringRef) daemonBundleID,
auth,
&cfError);
if (!blessed) {
NSError *error = (__bridge NSError *)cfError;
NSLog(#"Failed to bless PacketTool: %#", error);
[NSApp presentError: error];
return FALSE;
}
When you declare cfError you shouldn't use pointer *, you should use:
CFErrorRef cfError = nil;
NSError *error = (__bridge NSError *)cfError;
In the other way it works like that:
NSError *error = nil;
CFErrorRef ref = (__bridge CFErrorRef) error;
Hope this help.
On Dec 7 '13 at 16:05, Tom added:
Update: Thanks to Greg, correct code is now:
CFErrorRef cfError = nil;
BOOL blessed = SMJobBless(kSMDomainSystemLaunchd,
(__bridge CFStringRef) daemonBundleID,
auth,
&cfError);
if (!blessed) {
NSError *error = (__bridge NSError *)cfError;
NSLog(#"Failed to bless PacketTool: %#", error);
[NSApp presentError: error];
return FALSE;
}
I know that this post is 2 years old but it is wrong and I don't want other programmers copying that wrong code. This code leaks memory as the CFError is never released!
CoreFoundation has no automatic memory management, also not when using ARC. ARC only applies to Obj-C objects. And CoreFoundation doesn't know autorelease or autorelease pools, so CoreFoundation objects (CFStringRef, CFNumberRef, CFErrorRef, etc.) you get from CoreFoundation functions are never autoreleasing. They either don't need to be released at all or it's up to you to release them. And in case of out errors (CFErrorRef *), it's up to you to release them.
See also https://stackoverflow.com/a/8628267/15809
The first part of the code is correct:
CFErrorRef cfError = nil;
BOOL blessed = SMJobBless(
kSMDomainSystemLaunchd,
(__bridge CFStringRef)daemonBundleID,
auth, &cfError
);
But then you need to understand bridge casting. The simplest form or bridge casting is just __bridge and this cast tells ARC "don't do anything". If you do this
NSError * error = (__bridge NSError *)cfError;
You tell ARC: "Cast cfError to error but don't manage the memory of error after the cast, it's none of your business."
If you do this, you are still responsible for releasing the CFErrorRef! That means once you are done with cfError and with error ("and" as both point to the same object and if that is destroyed both pointers become invalid), you must do this:
CFRelease(cfError);
Otherwise you are leaking memory!
Alternatively you can tell ARC to manage the memory for you but then you need a different cast. If you cast like that
NSError * error = (__bridge_transfer NSError *)cfError;
you tell ARC: "Cast cfError to error and then its up to you to manage the memory of error."
Now you don't need to release anything since as soon as error goes out of scope, ARC will release it for you. And because error and cfError are in fact the same object, releasing error also releases cfError, so now you don't need to release anything. As the name implies, this cast "transfers" the object to ARC. Once that is done, you must not use cfError at all any longer as you cannot say for sure when exactly ARC will release error and as soon as it does, cfError is an invalid pointer, using it can easily crash your whole app.
Same holds true if you cast into the other direction. If you do
NSError * error = ...;
CFErrorRef cfError = (__bridge CFErrorRef)error;
ARC will still manage the memory of error, which is dangerous as, see above, when ARC decides it can destroy error, cfError will also become invalid. It is okay if you only use cfError in the current scope, but if your CFErrorRef needs survive regardless what ARC does, then you do this cast:
NSError * error = ...;
CFErrorRef cfError = (__bridge_retained CFErrorRef)error;
This tells ARC: "Retain error once, then cast error to cfError and don't ever balance this initial retain."
So even when error goes out of scope, it won't be released by ARC as the retain counter of the object won't become 0, it will still at least be 1 because of that cast. It's now up to you to take care of memory management and that means once you are done with cfError, you must release it:
CFRelease(cfError);

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

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

NSDictionary causing EXC_BAD_ACCESS

I am trying to call objectForKey: on an nsdictionary ivar, but I get an EXC_BAD_ACCESS error.
The nsdictionary is created using the JSON-framework and then retained. The first time I use it (just after I create it, same run loop) it works perfectly fine, but when I try to access it later nothing works. I am doing this code to try to figure out what is wrong:
if (resultsDic == nil) {
NSLog(#"results dic is nil.");
}
if ( [resultsDic respondsToSelector:#selector(objectForKey:)] ) {
NSLog(#"resultsDic should respond to objectForKey:");
}
The dictionary is never nil, but it always crashes on respondsToSelector. any ideas?
addition:
These are the other places, besides above, that the dictionary gets interacted with:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
//get the data in a usable form
NSString *jsonString = [[NSString alloc] initWithData:downloadedData encoding:NSUTF8StringEncoding];
resultsDic = [jsonString JSONValue];
[self processResults];
NSLog(#"Success. Received %d bytes of data",[downloadedData length]);
[downloadedData release];
[jsonString release];
}
- (void)processResults
{
NSArray *resultsArr = [resultsDic objectForKey:#"results"];
CLLocationCoordinate2D coordinate = [self coordinateFromResult:[resultsArr objectAtIndex:0]];
NSLog(#"lat: %f lng: %f", coordinate.latitude, coordinate.longitude);
}
- (void)dealloc {
[resultsDic release];
[super dealloc];
}
After somethings retain count is decreased to 0, the object gets deallocated. This is not the same as setting it to nil. It will not be nil. Whilst you can send messages to nil, sending a message to a released object will result in an EXC_BAD_ACCESS error. If you post some of the code where it is created and used, maybe we can help you debug it. Try retaining it twice at the beginning. it's nit an elegant solution, but it might work as a quick fix.
Sounds like a classic zombie. Run it again with the environment variable NSZombieEnabled set to YES (or use the Zombies instrument in Instruments.app). That should give you much more information about what's going on.