Why does this program take up so much memory? - objective-c

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.

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!

#property copy & manual memory management with an autorelease pool

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.

How to return an NSString * in Objective C (keep getting invalid summary)

So I presume this is a memory issue, here's the code:
- (NSString *)giveMeAStringGoddammit
{
NSString *s;
// switch statement to choose which string to assign to s, so essentially:
s = #"a string";
return s;
}
And the calling code:
NSString *aString;
aString = [self giveMeAStringGoddammit];
However after this call, aString has an invalid summary when debugged and crashes when run.
I suspect I'm missing a retain or something, can someone help? Thanks.
What you've got works just fine:
#import <Foundation/Foundation.h>
#interface Test : NSObject
- (NSString *)giveMeAStringGoddammit;
#end
#implementation Test
- (NSString *)giveMeAStringGoddammit
{
NSString *s;
// switch statement to choose which string to assign to s, so essentially:
s = #"a string";
return s;
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
Test *t = [[Test alloc] init];
NSLog(#"t says: %#", [t giveMeAStringGoddammit]);
}
return 0;
}
The output of this program is:
t says: a string
To make this a little more realistic, let's change it to use a property:
#import <Foundation/Foundation.h>
#interface Test : NSObject
#property(copy, nonatomic) NSString *string;
- (NSString *)giveMeAStringGoddammit;
#end
#implementation Test
#synthesize string;
- (NSString *)giveMeAStringGoddammit
{
NSString *s;
// switch statement to choose which string to assign to s, so essentially:
s = self.string;
return s;
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
Test *t = [[Test alloc] init];
t.string = #"Hello world!";
NSLog(#"t says: %#", [t giveMeAStringGoddammit]);
}
return 0;
}
This does what you'd expect:
t says: Hello world!
You have created a pointer object and it's expected to increment it's retain count whenever you referring them, for increasing retain count the string should be initiated and allocated with memory else you could use [NSString stringwithString:[self giveMeAStringGoddammit]. You can use this definition only when you exactly need it reference locally. because whenever you try to refer it out side the auto release pool will crash the app (hence it's not retained manually). So if you need to use it out side of the function, better use [NSString alloc]init] and then load your string to the pointer object. Well the way to make your code to work is add the lines NSString *aString = [NSString stringWithString:[self giveMeAStringGoddammit]];
NSLog(#"My Str:%#",aString); Hooray now the goddammit string was given......

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.

release of previously deallocated object issue

I have a function which use for read one single line from a csv file.
But I got a release of previously deallocated object error, or sometimes the it is "double free" error.
I try to track down which object causes this error base on the error memory address, but I failed to do this.
Here's the code:
#interface CSVParser : NSObject {
NSString *fileName;
NSString *filePath;
NSString *tempFileName;
NSString *tempFilePath;
//ReadLine control
BOOL isFirstTimeLoadFile;
NSString *remainContent;
}
#property(nonatomic,retain) NSString *fileName;
#property(nonatomic,retain) NSString *filePath;
#property(nonatomic,retain) NSString *tempFileName;
#property(nonatomic,retain) NSString *tempFilePath;
#property(nonatomic,retain) NSString *remainContent;
-(id)initWithFileName:(NSString*)filename;
-(BOOL)checkAndCopyFile:(NSString *)filename;
-(BOOL)checkAndDeleteTempFile;
-(NSString*)readLine;
-(NSArray*)breakLine:(NSString*)line;
#end
#implementation CSVParser
#synthesize fileName;
#synthesize filePath;
#synthesize tempFileName;
#synthesize tempFilePath;
#synthesize remainContent;
-(id)initWithFileName:(NSString *)filename{
//ReadLine control
isFirstTimeLoadFile = TRUE;
self.fileName = filename;
self.tempFileName = [[NSString alloc] initWithFormat:#"temp_%#",fileName];
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDir = [documentPaths objectAtIndex:0];
self.filePath = [documentDir stringByAppendingPathComponent:fileName];
self.tempFilePath = [documentDir stringByAppendingPathComponent:tempFileName];
if ([self checkAndCopyFile:fileName]) {
return self;
}else {
return #"Init Failure";
}
}
-(BOOL)checkAndCopyFile:(NSString *)filename{
BOOL isFileExist;
NSError *error = nil;
NSFileManager *fileManger = [NSFileManager defaultManager];
isFileExist = [fileManger fileExistsAtPath:filePath];
if (isFileExist) {
//Create a temp file for reading the line.
[fileManger copyItemAtPath:filePath toPath:tempFilePath error:&error];
return TRUE;
}else {
return FALSE;
}
}
-(NSString*)readLine{
NSError *error = nil;
//Read the csv file and save it as a string
NSString *tempFirstLine = [[[NSString alloc] init] autorelease];
NSString *stringFromFileAtPath = [[NSString alloc] init];
if (isFirstTimeLoadFile) {
NSLog(#"Into First Time");
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath
encoding:NSUTF8StringEncoding
error:&error];
isFirstTimeLoadFile = FALSE;
}else {
NSLog(#"Not First Time");
NSLog(#"Not First Time count:%d",[remainContent retainCount]);
stringFromFileAtPath = remainContent;
remainContent = nil;
}
if ([stringFromFileAtPath isEqualToString:#""]) {
[stringFromFileAtPath release];
return #"EOF";
}
//Get the first line's range
NSRange firstLineRange = [stringFromFileAtPath rangeOfString:#"\n"];
//Create a new range for deletion. This range's lenght is bigger than the first line by 1.(Including the \n)
NSRange firstLineChangeLineIncludedRange;
if (stringFromFileAtPath.length > 0 && firstLineRange.length == 0) {
//This is the final line.
firstLineRange.length = stringFromFileAtPath.length;
firstLineRange.location = 0;
firstLineChangeLineIncludedRange = firstLineRange;
}else {
firstLineRange.length = firstLineRange.location;
firstLineRange.location = 0;
firstLineChangeLineIncludedRange.location = firstLineRange.location;
firstLineChangeLineIncludedRange.length = firstLineRange.length + 1;
}
//Get the first line's content
tempFirstLine = [stringFromFileAtPath substringWithRange:firstLineRange];
remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange withString:#""];
[stringFromFileAtPath release];
error = nil;
return tempFirstLine;
}
And the following code shows how I use the class above:
CSVParser *csvParser = [[CSVParser alloc] initWithFileName:#"test.csv"];
BOOL isFinalLine = FALSE;
while (!isFinalLine) {
NSString *line = [[NSString alloc] init];
line = [csvParser readLine];
if ([line isEqualToString:#"EOF"]) {
isFinalLine = TRUE;
}
NSLog(#"%#",line);
[line release];
}
[csvParser release];
If I run the code, and finish the csv parsing, the App's main function will give me the double free error when it try to free the autorelease pool."* __NSAutoreleaseFreedObject(): release of previously deallocated object (0x6a26050) ignored"
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
Could someone help me solve this issue?
Thank you!
[pool release];
Do not use -retainCount.
The absolute retain count of an object is meaningless.
You should call release exactly same number of times that you caused the object to be retained. No less (unless you like leaks) and, certainly, no more (unless you like crashes).
See the Memory Management Guidelines for full details.
There are a few problems in your code:
you aren't following the correct init pattern. You should have a self = [super init...]; if (self) {...} in there somewhere.
tempFileName is a retain property and you assign it the result of alloc/init. It will be leaked.
An immutable empty string ([[NSString alloc] init]) is pretty much never useful. And, in fact, stringFromFileAtPath is being leaked (technically -- implementation detail wise there is an empty immutable singleton string and thus, no real leak, but.... still...)
Finally, the crash: your readLine method correctly returns an autoreleased object. Yet, your while() loop consuming the return value of readLine is also releaseing that return value, leading to a double-release and an attempt to free that which was already freed.
You should "build and analyze" your code. I bet the llvm static analyzer would identify most, if not all, of the problems I mentioned above (and probably some more I missed).
When building with the analyzer, do you have either "all messages" or "analyzer issues only" selected in the Build window? Because, looking at the code, I'm surprised the analyzer didn't catch the obvious problem with stringFromFileAtPath.
Excerpting the code, you have the following lines that manipulate stringFromFileAtPath:
NSString *stringFromFileAtPath = [[NSString alloc] init];
....
stringFromFileAtPath = [NSString stringWithContentsOfFile:tempFilePath
encoding:NSUTF8StringEncoding
error:&error];
....
stringFromFileAtPath = remainContent;
....
[stringFromFileAtPath release];
And remainContent is set by:
remainContent = [stringFromFileAtPath stringByReplacingCharactersInRange:firstLineChangeLineIncludedRange
withString:#""];
You are releasing an autoreleased object. By memory keeps going up, how are you measuring it? Don't use Activity Monitor as it is nearly as useless to developers as retainCount is misleading. Use Instruments.
Your tempFirstLine NSString object is declared with autorelease, and is returned as your NSString line, which is then released.
Try using this:
while (!isFinalLine) {
NSString *line = [csvParser readLine];
if ([line isEqualToString:#"EOF"]) {
isFinalLine = TRUE;
}
NSLog(#"%#",line);
}
Replac this:
NSString *stringFromFileAtPath = [[NSString alloc] init];
with this:
NSString *stringFromFileAtPath = nil;
and get rid of the [stringFromFileAtPath release] statements.
The first line creates a pointer to a new string object that you never use, because you immediately overwrite the pointer with a pointer to string objects from elsewhere, which you don't need to release because you don't own them/didn't create them. Since you are releasing them, you're getting a crash.
You make the same mistake with tempFirstLine.