The below code works fine, you can run it at your system for confirmation.
My question is, as you can see, the dealloc method is only called when the retain count reaches zero, meaning the memory is freed for the RetainTracker object. However, the issue is when I log the retain count in dealloc method, it still shows a retain count of 1. Why is this?
Here is my code:
#import <Foundation/Foundation.h>
#interface RetainTracker : NSObject
#end
#implementation RetainTracker
- (id)init {
if (self = [super init]) {
NSLog(#"init: Retain count of %lu",(unsigned long)[self retainCount]);
}
return self;
}
- (void)dealloc {
NSLog(#"Dealloc called bye bye!==>%lu",(unsigned long)self.retainCount);
[super dealloc];
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
// insert code here...
RetainTracker *myRetainTracker = [RetainTracker new];
[myRetainTracker retain]; // count-->2
NSLog(#"The retain count is ==>%lu",(unsigned long)myRetainTracker.retainCount);
[myRetainTracker release];// count -->1
NSLog(#"The retain count is ==>%lu",(unsigned long)myRetainTracker.retainCount);
[myRetainTracker retain];// count -->2
NSLog(#"The retain count is ==>%lu",(unsigned long)myRetainTracker.retainCount);
[myRetainTracker release];// count -->1
NSLog(#"The retain count is ==>%lu",(unsigned long)myRetainTracker.retainCount);
[myRetainTracker release];// count -->0
NSLog(#"The retain count is ==>%lu",(unsigned long)myRetainTracker.retainCount);
}
return 0;
}
Here are the logs:
init: Retain count of 1
The retain count is ==>2
The retain count is ==>1
The retain count is ==>2
The retain count is ==>1
Dealloc called bye bye!==>1
The retain count is ==>1
This is not surprising, the release code probably looks like this (in pseudocode):
- (void)release {
if (retainCount > 1) {
retainCount -= 1
} else {
// no need to set the retainCount to 0 here,
// the object now ends its existence
[self dealloc]
}
}
Also, your last NSLog is actually accessing an object that no longer exists which can result in a crash.
Note that the value read from retainCount should never be relied upon. It's just an implementation detail. It is much safer to think about retain and release as transfer of ownership.
From retainCount documentation:
Do not use this method.
and
This method is of no value in debugging memory management issues. (...) it is very unlikely that you can get useful information from this method.
Switch your project to ARC. Seriously. I have forgotten everything I knew about retain / release three years ago. Other than that, the retain count of an object in the process of being released is meaningless. It's pointless wondering about it.
Related
I need some clarifications on a crash I'm encountering using NSArray, blocks and Manual Reference Counting. My goal is to store blocks on a collection (NSArray in this case) in order to reuse them in the future.
I've setup a small sample to replicate the issue. In particular, I have a class Item that looks like the following:
#import <Foundation/Foundation.h>
typedef void(^MyBlock)();
#interface Item : NSObject
- (instancetype)initWithBlocks:(NSArray*)blocks;
#end
#import "Item.h"
#interface Item ()
#property (nonatomic, strong) NSArray *blocks;
#end
#implementation Item
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
NSMutableArray *temp = [NSMutableArray array];
for (MyBlock block in blocks) {
[temp addObject:[[block copy] autorelease]];
}
_blocks = [temp copy];
}
return self;
}
The usage is described below (I'm using in the app delegate).
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = ^() {
[weakSelf doSomething1];
};
MyBlock myBlock2 = ^() {
[weakSelf doSomething1];
};
NSArray *blocks = #[myBlock1, myBlock2];
// As MartinR suggested the code crashes even
// if the following line is commented
Item *item = [[Item alloc] initWithBlocks:blocks];
}
If I run the app, it crashes with an EXC_BAD_INSTRUCTION (note that I've already enabled All Exceptions breakpoints). In particular, the app stops in the main.
int main(int argc, const char * argv[]) {
return NSApplicationMain(argc, argv);
}
Note: As suggested by Ken Thomases, if you use bt command on llvm console, you are to see the back trace. In this case it shows the following:
-[__NSArrayI dealloc]
If I comment the [weakSelf doSomethingX]; it works without crashes (it does not mean that is correct).
Modifying the code a little bit like the following, all runs ok.
// Item does not do anymore the copy/autorelease dance
// since used in the declaration of the blocks
- (instancetype)initWithBlocks:(NSArray*)blocks
{
self = [super init];
if (self) {
_blocks = [blocks retain];
}
return self;
}
and
__block typeof(self) weakSelf = self;
MyBlock myBlock1 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
MyBlock myBlock2 = [[^() {
[weakSelf doSomething1];
} copy] autorelease];
NSArray *blocks = #[myBlock1, myBlock2];
Item *item = [[Item alloc] initWithBlocks:blocks];
What is the point here? I think I'm missing something but I don't know what.
Update 1
Ok. I'll try to recap my thoughts based on the comments with #Martin R and #Ken Thomases.
A block, by default, is created on stack if a copy message is not sent to it (ARC does this for us) in order to move it on the heap. So, the situation in this case is the following. I create an autorelease array and I add two blocks where retain is called in a implicit manner. When the applicationDidFinishLaunching method finishes is execution, the blocks, since created on the stack (they are automatic variables) disappear. In a later moment, the array called blocks will be released since has been marked as autorelease. So, it will crash since it will send a release object to blocks that do not exist anymore.
So, my question is the following: What does it mean to send a retain message to a block that is on the stack? Why the array is the source of the crash (see the back trace)? In other words, since a block is on the stack, will it bump the retain count of it? And when it goes out of scope? In addiction, why if I comment the [weakSelf doSomething1] line the code works without problems? Not very clear to me this part.
You are sticking an object from the stack into an autoreleased array. BOOM ensues.
Consider:
typedef void(^MyBlock)();
int main(int argc, char *argv[]) {
#autoreleasepool {
NSObject *o = [NSObject new];
MyBlock myBlock1 = ^() {
[o doSomething1];
};
NSLog(#"o %p", o);
NSLog(#"b %p", myBlock1);
NSLog(#"b retain %p", [myBlock1 retain]);
NSLog(#"b copy %p", [myBlock1 copy]);
NSLog(#"s %p", ^{});
sleep(1000000);
}
}
Compiled/run as -i386 (because the #s are smaller and more obvious):
a.out[11729:555819] o 0x7b6510f0
a.out[11729:555819] b 0xbff2dc30
a.out[11729:555819] b retain 0xbff2dc30
a.out[11729:555819] b copy 0x7b6511a0
a.out[11748:572916] s 0x67048
Since the object is at 0x7b, we can assume that is the heap. 0xb is really high memory and, thus, the stack.
The retain doesn't cause a copy (because doing so would have invariably led to leaks) and retain on a stack based object is meaningless.
If you change the [o doSomething1]; to [nil doSomething1]; then that becomes a static block and that lives in readonly mapped memory (readonly-executable pages from the mach-o's TEXT segment) and, thus, there is no allocation to deallocate and retain/release/autorelease are no-ops.
As you can see, the static block ended up around 0x67048 (this number may change from run to run, btw, for a variety of reasons. Low in memory.
In fact, because of the sleep(), we can run vmmap against the a.out process and see:
==== Writable regions for process 11772
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
__DATA 00067000-00068000 [ 4K] rw-/rwx SM=ZER /tmp/a.out
That is, the static block was in the first 4K segment of mapped writable regions from the mach-o file. Note that this doesn't mean the code is in that writable region (SECURITY HOLE if it were). The code is in the TEXT segment mapped into the readable regions.
Here is implement of a setter method:
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
If retainCount of _count is <=0, how it can release ?
The only valid object that can ever have a retain count of 0 is nil. And sending any message to nil just returns 0 without doing anything else, so that case is covered.
If you mean "how can this work with a deallocated object" — well, it can't. And a deallocated object's retain count isn't really 0, because the object doesn't exist anymore — it's been destroyed and is now just a chunk of memory — so it doesn't have any attributes. Doing anything with a deallocated object is invalid and what will happen is undefined.
Imagine a class with a retainCount instance variable.
#implementation MyClass
{
NSUInteger retainCount;
}
- (id) retain {
retainCount++;
return self;
}
- (void) release {
if (retainCount > 1)
retainCount--;
else
[self dealloc];
}
...
#end
Once an object is deallocated, it is dead, gone, done for, etc... Thus, there is no point in ever decrementing the retainCount to 0 because, by definition, the object is going to be deallocated and working with a deallocated object is undefined behavior.
The above is the exact logic of NSObject, but a completely different implementation (you really wouldn't want to see NSObject's actual implementation -- it is quite painful).
The other source of confusion appears to be what a reference means.
NSObject *foo;
char *bar;
NSUInteger baz;
For all intents and purposes, the above three variable declarations behave identically [in manual retain/release].
When you say bar = "Hello, World!";, you are telling the compiler 'copy the address of the memory that holds the string "Hello, World!" into the memory named bar". Same for foo, only you are copying the address of memory that holds an instance of the class NSObject.
Now, baz may seem different. But it really isn't except that it holds numbers, not addresses. But, really, an address is a number!
So, in a setter::
- (void)setCount:(NSNumber *)newCount {
// increment newCount's retain count
[newCount retain];
// decrement the _count's retain count (which may cause it to be deallocated or not)
[_count release];
// copy the address of the memory that holds the NSNumber instance referenced
// by `newCount` into the instance variable `_count`.
_count = newCount;
}
There is nothing magical about that assignment [under manual retain release]. It is just copying a number from one variable to the other. The objects are not impacted at all by this.
I have a question.
I first created an object which extends NSObject, I provided overrides for the description and dealloc methods. Here's my Employee.m file:
#implementation Employee
.....
-(NSString *)description
{
return [NSString stringWithFormat:#"Employ ID: %d has $%d value of assets", [self employeeID], [self valueOfAssets]];
}
-(void)dealloc
{
NSLog(#"deallocating.. %#", self);
[super dealloc];
}
In my main.m, I first created an NSMutableArray to hold a list of Employee objects:
NSMutableArray *employees = [[NSMutableArray alloc] init];
for (int i =0; i< 10; i++)
{
// Create an instance of Employee
Employee *person = [[Employee alloc] init];
// Give the instance varaible interesting values
[person setEmployeeID:i];
[employees addObject: person];
}
and at the end I set employees to nil
employees = nil;
I expected the dealloc method of each Employee object to be called and I would see some logs like:
deallocating.. Employ ID 0 has value.....
deallocating.. Employ ID 2 has value.....
....
However, I didn't see any logs and if I set a breakpoint on the dealloc method, the breakpoint is never hit.
Any thoughts?
A couple of observations:
person = nil does not release an object in non-ARC code. It will in ARC code (at least if it's strong).
In ARC, local objects will be released for you automatically when they fall out of scope. In non-ARC, objects falling out of scope will not be released for you (and if you don't have other references to those objects elsewhere, you'll end up with a leak).
Adding an item to a mutable array will increase the retain count of the item, so even if you include a release in your non-ARC code, the object won't be released until the retain count drops to zero (accomplished by not only releasing the person objects after you add them to the array, but also removing them from the array.
Thus, given that this is non-ARC code, it could be something like:
- (void)testInNonArcCode
{
NSMutableArray *employees = [[NSMutableArray alloc] init]; // employees retain count = +1
for (int i =0; i< 10; i++)
{
//create an instance of Employee
Employee *person = [[Employee alloc] init]; // person retain count = +1
//Give the instance varaible interesting values
[person setEmployeeID:i];
[employees addObject: person]; // person retain count = +2
[person release]; // person retain count = +1 (YOU REALLY WANT TO DO THIS OR ELSE OR NON-ARC PROGRAM WILL LEAK)
// person = nil; // this does nothing, except clears the local var that's limited to the for loop scope ... it does nothing to reduce the retain count or improve memory management in non-ARC code, thus I have commented it out
}
// do whatever you want
[employees removeAllObjects]; // this will remove all of the person objects and they will have their respective retain counts reduced to 0, and therefore the Employee objects will be released
[employees release]; // employees array's own retain count reduced to zero (and will now be dealloced, itself)
}
In ARC code:
- (void)testInArcCode
{
NSMutableArray *employees = [[NSMutableArray alloc] init]; // employees retain count = +1
for (int i =0; i< 10; i++)
{
//create an instance of Employee
Employee *person = [[Employee alloc] init]; // person retain count = +1
//Give the instance varaible interesting values
[person setEmployeeID:i];
[employees addObject: person]; // person retain count = +2
// person = nil; // this would reduce person retain count to +1 (but unnecessary in ARC because when person falls out of scope, it will have it's retain count automatically reduced)
}
// do whatever you want
[employees removeAllObjects]; // this will remove all of the person objects and they will have their respective retain counts reduced to 0, and therefore will be released
// [employees release]; // not permitted in ARC
// employees = nil; // this would effectively release employees, but again, not needed, because when it falls out of scope, it will be released anyway
}
The proper way of freeing objects is to do
[employees release];
Setting it to nil will not release the memory.
By virtue of you being allowed to call [super dealloc], I can assume that you are not using Automatic Reference Counting. This means that you need to explicitly pair every alloc you write with a balancing release call. For you, when you make the array nil, you essentially leaked all of the memory for the employees. You need to loop over the array again to release them all, or better yet since you are learning... Start as soon as possible writing ARC code.
It may be important to note that ARC was created for exactly this kind of situation; it makes sense to our brains, and now it can be a reality if you use the latest tools.
I'm a java programmer new to Objective C, so please be gentle :)
I'getting an errorr message saying EXC_BAD_ACCESS on calling release on an object:
I read documentation and threads on this site, but I see data that's confusing me
- (void) dealloc {
NSLog(#"dealloc in image Retain count: %i", [image retainCount]);
[image release];//method throwing EXC_BAD_ACCESS
..............
}
the logged retain count is: 1
In the code that is causing the dealloc I have:
UIImage *scrn = [[UIImage alloc] initWithCGImage:newImage];
NSLog(#"in after instantiation Retain count: %i", [scrn retainCount]);// logs retain count of 1
CGImageRelease(newImage);
Decoder *d = [[Decoder alloc] init];
.....
NSLog(#"in before decoding Retain count: %i", [scrn retainCount]);// logs retain count of 1
decoding = [d decodeImage:scrn cropRect:cropRect] == YES ? NO : YES;
NSLog(#"in after decoding Retain count: %i", [scrn retainCount]); // logs retain count of 2
[d release]; // this line causes invocation of dealloc on the previous code sniplet
[scrn release];
In decodeImage the following is going on:
- (BOOL) decodeImage:(UIImage *)i cropRect:(CGRect)cr {
NSLog(#"Decoder.mm.decodeImage initial Retain count i : %i retaincount image %i", [i retainCount], [image retainCount]); //logs: Decoder.mm.decodeImage initial Retain count i : 1 retaincount image 0
[self setImage: i];
NSLog(#"Decoder.mm.decodeImage after setting image Retain count i : %i retaincount image %i", [i retainCount], [image retainCount]);//logs: Decoder.mm.decodeImage after setting image Retain count i : 2 retaincount image 2
.......
return [self decode];
}
There are several things puzzeling me:
From what understood the retainCount is increased by calling retain or by instantiating a new object, not by assigningment of one var to another as is done in self setImage: i]; however i see that the retaincount is increased by one
Before calling [d release] the logged retainCount is 2, in the method dealloc the count is 1
If the count is 1, why do I get the EXC_BAD_ACCESS ???
Edit: added additional code as requested
#implementation Decoder
#synthesize image;
The setting of image is mentioned in the third code sniplet above.
Retain count can also be incremented by the system (for build-in types) and by properties that are defined with the retain or copy attributes. You're only responsible for the ones you cause (not the system retains), but don't depend on the retain count when trying to determine why you're getting EXC_BAD_ACCESS. XCode has some good build-in analysis tools that are better for tracking down access errors.
One important thing to note: your retaincount will never go below 1 even if you release when the count is 1.
See this question for good information on retaincount.
This is a followup question from a previous question, which is hopefully a little clearer. I am just curious how the code presented below is working, specifically is the variable myString getting released. It does not look like it is from the output?
CODE
// IMPLEMENT
#implementation CelestialBody
- (void)setName:(NSString *)newName{
if(name != newName) {
[name release];
name = [newName copy];
}
}
- (void)dealloc{
[name release];
name = nil;
[super dealloc];
}
#end
.
// ------------------------------------------------------------------- **
// MAIN: 30th September 2009
// ------------------------------------------------------------------- **
#import <Foundation/Foundation.h>
#import "CelestialBody.h"
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
CelestialBody *newPlanet = [[CelestialBody alloc] init];
NSString *myString = [[NSString alloc]initWithFormat:#"go home"];
NSLog(#"RetainCount_1: %d",[myString retainCount]);
[newPlanet setName: myString];
NSLog(#"RetainCount_2: Incremented by copy in setName");
// Clean up
NSLog(#"RetainCount_2: %d -Before Release",[myString retainCount]);
[newPlanet release];
[myString release];
[pool drain];
NSLog(#"RetainCount_1: %d -After Release",[myString retainCount]);
return 0;
}
// ------------------------------------------------------------------- **
OUTPUT
Running…
2009-10-01 09:28:50.395 RetainCount_1: 1
2009-10-01 09:28:50.399 RetainCount_2: Incremented by copy in setName
2009-10-01 09:28:50.399 RetainCount_2: 2 -Before Release
2009-10-01 09:28:50.400 RetainCount_1: 1 -After Release
Debugger stopped.
I am currently re-reading the Memeory Management Guide to try and see what I have missed.
many thanks
EDIT
Just added a the release to the dealloc, It looks like that was what I was missing.
- (void)dealloc{
[name release];
name = nil;
[super dealloc];
}
gary
is the variable myString getting released.
[myString release];
All signs point to yes.
It does not look like it is from the output?
NSLog(#"RetainCount_2: %d",[myString retainCount]);
[myString release];
Your NSLog statement's output doesn't reflect the release message because the release message hasn't happened yet.
Also, don't worry about retain counts. They can be very misleading. As long as you follow Cocoa's rules and don't create any ownership cycles (A owns B owns C owns A), you'll rarely have a problem.
This is not an answer to your question, per se, but an explanation of what you're seeing: The last call to retainCount is sent to a deallocated object, which is undefined behavior. The object happens not to have been overwritten yet, so it still kindasorta "works" in the sense that the method dispatch can still see the old data that's there and doesn't realize it's invalid. You will never get back 0 from calling retainCount, because such an object can't exist.
I guess [newName copy] doesn't actually copy the NSString, because NSString is immutable? I never thought about this, but it makes sense to me.
At the end of your program you're releasing newPlanet and myString. My question is, are you releasing the instance variable name in the -dealloc method of CelestialBody? If you're not, then I believe you're leaking memory there.
Create myString: the NSString's retainCount is 1
[newPlanet setName:]: the NSString's retainCount is 2
[newPlanet release]: the NSString's retainCount is 2 ? I guess you're not releasing it in -dealloc.
[myString release]: the NSString's retainCount is 1