#property copy & manual memory management with an autorelease pool - objective-c

I have a class that copy's an NSString and print's it's retain count and address.
#interface TestStringPointer : NSObject
#property (copy) NSString *stringTest;
- (void)printPointer;
#end
#implementation TestStringPointer
- (void)printPointer {
NSLog(#"PrintPointer:%p\n RetainCount:%lu", _stringTest, [_stringTest retainCount]);
}
#end
In my main function I was doing a bit of investigation on the String's pointer and ran into an issue.
int main(int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
TestStringPointer *test = [[TestStringPointer alloc] init];
NSString *myString = [NSString stringWithUTF8String:"Hello World"];
NSLog(#"MyString: %p \n RetainCount:%lu", myString, [myString retainCount]);
[test setStringTest:myString];
[test printPointer];
[myString release];
[pool drain];
while (1) {
[test printPointer];
}
return 0;
}
When I debug the app it crashes the 3rd time through the while loop (the number of times through the loop varies). I understand that the copy doesn't occur cause the string isn't mutable.
After it's released in main, I would've expected it to go back to 1.
1) If an object isn't autoreleased, is it still affected the autorelease pool?
2) Wouldn't having a max retain count prevent the object from being drained in the pool, or does that flag it for deletion?
3) Shouldn't the copy have stepped in at some point and actually made a copy before it was deleted?
StringPointerTest[2253:303] MyString: 0x100100f60 RetainCount:1
StringPointerTest[2253:303] PrintPointer:0x100100f60 RetainCount:2
StringPointerTest[2253:303] PrintPointer:0x100100f60
RetainCount:1152921504606846975
StringPointerTest[2253:303] PrintPointer:0x100100f60 RetainCount:1152921504606846975
If I modify main and remove the pool
int main(int argc, const char * argv[])
{
TestStringPointer *test = [[TestStringPointer alloc] init];
NSString *myString = [NSString stringWithUTF8String:"Hello World"];
NSLog(#"MyString: %p \n RetainCount:%lu", myString, [myString retainCount]);
[test setStringTest:myString];
[test printPointer];
[myString release];
while (1) {
[test printPointer];
}
return 0;
}
All is right... Forever...
StringPointerTest[423:303] MyString: 0x10010a670 RetainCount:1
StringPointerTest[423:303] PrintPointer:0x10010a670 RetainCount:2
StringPointerTest[423:303] PrintPointer:0x10010a670 RetainCount:1
...

The mistake is when you release myString. This is wrong because stringWithUTF8String returns an autoreleased string (remember that every method has the autoreleased version: a static method, and the non-autoreleased version: init or initWithSomething: ). So when the autorelease pool gets drained, myString retain count goes to zero and the object gets deallocated (maybe later, you don't know exactly when, the fact that it crashes at the 3rd loop iteration is casual).
So you solve the problem by calling alloc + initWithUTF8String: instead of stringWithUTF8String:. The fact that you see an incredibly high retain count is just dued to the fact that the memory has been freed and maybe written again, before the object gets really deallocated, it's just a pitfail, you don't own the object anymore.

Related

a base use of Objective-C's retainCount

We both know that the retain method will +1 retainCount, release will -1 retainCount, if retainCount == 0, the object dealloc.
But I meet a problem, the following code run have the ridiculous result
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property(nonatomic, retain) NSString *name;
#property(nonatomic, assign) NSInteger age;
#end
#implementation Person
- (id)init
{
self = [super init];
if (self) {
self.name = #"name";
self.age = 20;
}
return self;
}
- (void)dealloc
{
NSLog(#"dealloc");
[super dealloc];
}
#end
int main(int argc, const char * argv[])
{
Person *per1 = [[Person alloc] init];
NSLog(#"retainCount = %lu -- 1",[per1 retainCount]);
[per1 release];
[per1 retain];
NSLog(#"retainCount = %lu -- 2",[per1 retainCount]);
return 0;
}
The result is:
2014-01-11 21:56:23.887 blockTest[1287:303] retainCount = 1 -- 1
2014-01-11 21:56:23.889 blockTest[1287:303] dealloc
2014-01-11 21:56:23.889 blockTest[1287:303] retainCount = 2 -- 2
I don't use the ARC. Why I get this result?
Isn't the app should crash?
What's happening here is that although the object has been deallocated, the memory where it's stored hasn't been overwritten yet, so the later calls still work because the data is temporarily still there.
The reason you get a retainCount of 2 at the end is that when you send release to an object with a retainCount of 1, they just dealloc the object straight away without bothering to decrement the internal count to zero.
You shouldn't ever expect that this property returns any meaningful result.
It doesn't matter what you do, it's not your job.
See this for more information whentouseretaincount.com
Once an object is deallocated, you cannot send any further messages to that object. In your case, your object is deallocated once you send the release message to it. Sending any further messages, including retain and retainCount, is undefined behavior.
For example, on my system, the following code:
int main(int argc, const char * argv[])
{
Person *per1 = [[Person alloc] init];
[per1 release];
Person *per2 = [[Person alloc] init];
per2.name = #"Something Else";
[per1 retain];
NSLog(#"per1.name = %#",per1.name);
return 0;
}
prints:
per1.name = Something Else
More undefined behavior!

Unexpected retainCount

Have a look at this code-snippet with a simple retain/release scenario:
#import <Foundation/Foundation.h>
#interface SomeClass : NSObject
#end
#implementation SomeClass
#end
int main(int argc, const char * argv[])
{
SomeClass *aClass = [[SomeClass alloc] init];
NSLog(#"retainCount: %lu", [aClass retainCount]);
[aClass retain];
NSLog(#"retainCount: %lu", [aClass retainCount]);
[aClass release];
NSLog(#"retainCount: %lu", [aClass retainCount]);
[aClass release];
NSLog(#"retainCount: %lu", [aClass retainCount]);
return 0;
}
That's the resulting output:
2013-04-29 17:33:50.695 retainCount: 1
2013-04-29 17:33:50.697 retainCount: 2
2013-04-29 17:33:50.697 retainCount: 1
2013-04-29 17:33:50.698 retainCount: 1
The last retainCount should either be "0" or the app should crash. Why is the result "1" ?!
http://www.whentouseretaincount.com
Messaging a deallocated object is undefined behavior. It might crash, it might work, it might do something completely unexpected.
Upon deallocation, your program won't waste any cycles mucking with the freshly deallocated memory (unless you turn on malloc scribble), thus the undefined part of the behavior.
Nor will your program waste any cycles decrementing the retain count to 0; the object is going to be deallocated anyway, why bother?

Assigning objects to variable outside a block

The following code crashes, since the contents of sentence go away when the final block exits.
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// simple block test - just iterate over some items and
// add them to a string
NSArray *items = [NSArray arrayWithObjects:#"why ", #"must ", #"this ",nil];
__block NSString *sentence = #"";
[items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
sentence = [sentence stringByAppendingFormat:#"%#",obj];
}];
// crash!
NSLog(#"Sentence is %#",sentence);
[pool drain];
return 0;
}
What is the correct / idiomatic way to make this work?
Ok, I went away and played with Xcode for a bit, and here's a model of what's going on, that seems to match what I'm seeing.
The block I used above isn't doing anything special, but the enumerateObjectsUsingBlock code appears to have its own NSAutoreleasePool, so that seems to be what was causing dealloc to be called on objects alloc'ed, but autoreleased inside the block.
The following code matches in behavior what I'm seeing above:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// simple block test - just iterate over some items and
// add them to a string
typedef void (^AccArrayBlock)(id obj, int idx, BOOL *stop);
// items to 'process'
NSArray *items = [NSArray arrayWithObjects:#"why ", #"must ", #"this ",nil];
int idx = 0;
BOOL doStop = NO;
// make sentence mutable, so we can assign it inside block
__block NSString *sentence = #"";
// make a similar block to what we'd pass to enumerate...
AccArrayBlock myBlock = ^(id obj, int idx, BOOL *stop)
{
// returns and assigns an autoreleased string object
sentence = [sentence stringByAppendingFormat:#"(%d) %# ",idx,obj];
};
// enumerate items and call block
for (NSString *item in items) {
// create a pool to clean up any autoreleased objects in loop
// remove this line, and the sentence will be valid after loop
NSAutoreleasePool *innerPool = [[NSAutoreleasePool alloc] init];
myBlock(item, idx++, &doStop);
// drain the pool, autorelease objects from block
[innerPool drain];
if (doStop) {
break;
}
}
// faults if we drained the pool
// Program received signal: “EXC_BAD_ACCESS”.
NSLog(#"Sentence is %#",sentence);
[pool drain];
return 0;
}
If I remove the innerPool object, then the code works as I originally expected, and presumably the NSRunLoop pool will eventually clean up the various NSString objects.
NOTE: This thread is now the number 2 Google result for 'enumerateObjectsUsingBlock autorelease':
Google 'enumerateObjectsUsingBlock+autorelease'
The first result confirms this answer. Thanks all.
Ok so I'm not 100% sure what's going on there but in the mean time it works if you change
NSArray *items = [NSArray arrayWithObjects:#"why ", #"must ", #"this ",nil];
NSMutableString *sentence = [[NSMutableString alloc] init];
[items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
[sentence appendFormat:#"%#",obj];
}];
NSLog(#"Sentence is %#",sentence);
[sentence release]; sentence = nil;
Updated thanks to #nacho4d
As you mentioned, I suspect this is crashing when the autorelease pool runs, as it probably does in enumerateObjectsUsingBlock:. This will be annoying to work around if you have a __block variable. You could use an NSMutableString instead, or simply do this, which is cleaner anyway:
for (id obj in items)
{
sentence = [sentence stringByAppendingFormat:#"%#",obj];
}
Alternatively, if you use ARC, the compiler should eliminate the problem for you.

NSMutableArray function call leaking ..?

maybe someone can help me finding why his code is leaking ..
im calling the getNotes function, wich is returning a autorelease NSMutableArray
notesArray = [[noteManager getNotes:id] retain];
notesArray is a property declared in my header file
#property (nonatomic, retain) NSMutableArray* notesArray;
this is the stripped version of the getNotes function
- (NSMutableArray*) getNotes:(NSString *)id {
NSMutableArray* rArr = [[NSMutableArray alloc] init];
for (NSString* sNote in noteArray) {
myNote* note = (myNote*)[NSKeyedUnarchiver unarchiveObjectWithFile:sFile];
[rArr addObject:note];
}
return [rArr autorelease];
}
the [rArr addObject:note]; is 100% leaking ..
why? they are all autoreleased?
the myNote class just a class with some properties, nothing special ...
It may be that you already have notes stored in notesArray and they are not getting released before setting it again.
Try changing this
notesArray = [[noteManager getNotes:id] retain];
//to
self.notesArray = [noteManager getNotes:id];
Does notesArray get released at some point? Instead of manually retaining you can use the dot syntax:
self.notesArray = [noteManager getNotes:id];
instead of:
notesArray = [[noteManager getNotes:id] retain];
for (NSString* sNote in noteArray) {
myNote* note = (myNote*)[NSKeyedUnarchiver unarchiveObjectWithFile:sFile];
[rArr addObject:note];
}
Here your notes are autoreleased and shoud not produce memory leak, but autorelease does not happen immediately. This can become a problem in a long loops. You should use your own autorelease pool to avoid such situations. Like this
for (NSString* sNote in noteArray) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
myNote* note = (myNote*)[NSKeyedUnarchiver unarchiveObjectWithFile:sFile];
[rArr addObject:note];
[pool drain];
}

Why does this program take up so much memory?

I am learning Objective-C. I am trying to release all of the memory that I use. So, I wrote a program to test if I am doing it right:
#import <Foundation/Foundation.h>
#define DEFAULT_NAME #"Unknown"
#interface Person : NSObject
{
NSString *name;
}
#property (copy) NSString * name;
#end
#implementation Person
#synthesize name;
- (void) dealloc {
[name release];
[super dealloc];
}
- (id) init {
if (self = [super init]) {
name = DEFAULT_NAME;
}
return self;
}
#end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Person *person = [[Person alloc] init];
NSString *str;
int i;
for (i = 0; i < 1e9; i++) {
str = [NSString stringWithCString: "Name" encoding: NSUTF8StringEncoding];
person.name = str;
[str release];
}
[person release];
[pool drain];
return 0;
}
I am using a mac with snow leopard. To test how much memory this is using, I open Activity Monitor at the same time that it is running. After a couple of seconds, it is using gigabytes of memory. What can I do to make it not use so much?
Firstly, your loop is incorrect. +stringWithCString:… is not an +alloc/+new…/-copy method, so you should not -release it.
Either one of these are correct:
Don't -release:
str = [NSString stringWithCString: "Name" encoding: NSUTF8StringEncoding];
person.name = str;
Use -init:
str = [[NSString alloc] initWithCString: "Name" encoding: NSUTF8StringEncoding];
person.name = str;
[str release];
Similarly, in -[Person init]:
- (id) init {
if ((self = [super init])) {
name = [DEFAULT_NAME copy]; // <----
}
return self;
}
Now, if you use variant #1, the memory should rise up to gigabytes as you have seen before, while variant #2 should be a rather constant, small value.
The difference is because
str = [NSString stringWithCString: "Name" encoding: NSUTF8StringEncoding];
is equivalent to
str = [[[NSString alloc] initWithCString:......] autorelease];
An -autoreleased object means "transfer the ownership to the nearest NSAutoreleasePool, and let it release it later".
How late? By default, it's when the current run loop ticked once. However, you did not have an explicit run loop here*, so the run loop did not run. The autorelease pool never have a chance to clear up these 109 allocated temporary strings.
However, for variant #2, the temporary strings are immediately released, so the temporaries won't fill up the memory. (We don't need to wait for the pool to flush — there's no pools involved.)
Note:
*: A run loop is a unique loop attached to each running thread. If you write a CLI utility, there's seldom need to have a run loop.