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?
Related
I have a typedef
typedef void(^MyCompletionBlock)(NSDictionary *info);
- (void)populateWithCompletion:(MyCompletionBlock)completion
{
NSMutableArray *array = [NSMutableArray new];
[array addObject:completion];
[array removeObject:completion];
NSAssert(array.count == 0, "Why");
}
The object is not removed from the array, the pointer to completion is something like
completion MyCompletion 0x0000000102bcf30c
but when inspecting the contents of NSArray I see
[0] __NSMallocBlock__ * 0x105900610 0x0000000105900610
How do I get the underlying function pointer so that I can remove it from the array? Also, why is the underlying function pointer added to the array instead of my typedef?
Can't reproduce. Here is the complete code of my view controller:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self populateWithCompletion:^(NSDictionary *info) {
NSLog(#"%#", info);
}];
NSLog(#"%#", #"got to here");
// Do any additional setup after loading the view, typically from a nib.
}
typedef void(^MyCompletionBlock)(NSDictionary *info);
- (void)populateWithCompletion:(MyCompletionBlock)completion
{
NSMutableArray *array = [NSMutableArray new];
[array addObject:completion];
[array removeObject:completion];
NSAssert(array.count == 0, #"Why");
}
#end
Launched the app. Console says "got to here".
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!
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.
Can anyone help me figure out where I should be releasing exerciseArray? I get a crash in dealloc and when I release just before calling sortedArray. This code is called many times.
//Set exerciseArray
review.exerciseArray = [[workout assignedExercises] allObjects];
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(#"viewWillAppear");
NSString *workoutName = [event.assignedWorkout valueForKey:#"name"];
self.title = workoutName ;
NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"index"
ascending:YES] autorelease];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[sortedArray release];
sortedArray= [exerciseArray sortedArrayUsingDescriptors:sortDescriptors];
NSLog(#"*****SORTEDARRAY****** %d",[sortedArray retainCount]);
for (Exercise *ex in sortedArray){
NSLog(#"%# %# %#",ex.name , ex.repCount, ex.weight);
}
NSLog(#"*********************");
[sortedArray retain];
[self.tableView reloadData];
}
- (void)dealloc {
[super dealloc];
[managedObjectContext release];
[event release];
}
First things first: You should move the [super dealloc] call to the end of your dealloc method. Handle your custom variables first, and then the last thing you do is push the dealloc up to the superclass to handle the rest of the cleanup.
Now, I'm concerned about the [sortedArray release] and [sortedArray retain] in there. What you should be doing, to save you the semantics of implementing retain/release on your sortedArray ivar, is declaring a property for sortedArray like so:
// this goes in your #interface
#property (nonatomic, retain) NSArray *sortedArray;
// this goes in your #implementation
#synthesize sortedArray;
Now you can set sortedArray easily without worrying about retaining or releasing:
// note the use of self, this is required
self.sortedArray = [exerciseArray sortedArrayUsingDescriptors:sortDescriptors];
You can remove the release and retain calls now, as this is handled automatically. Make sure to add a line to your dealloc method to clean up the variable, too..
self.sortedArray = nil;
..which will call release on your array for you.
EDIT: This also applies to exerciseArray, which was your actual question. Wherever there's a Cocoa class involved, you can simplify memory management using #property (retain) and #synthesize. For NSInteger and other primitive types, or when you don't want to hold an owning reference to the object, use #property (assign) instead.
I'm trying to clean my app from leaks with Leak instrument.
It shows me leaks on xml parser (TBXML).
Here is a class I'm going to create upon the parsing:
#interface GraphPoint : NSObject {
NSString* x;
NSString* y;
}
#property (nonatomic, copy) NSString* x;
#property (nonatomic, copy) NSString* y;
#end
#implementation GraphPoint
#synthesize x, y;
... some calculations
- (void) dealloc
{
[x release];
[y release];
[super dealloc];
}
#end
In the parser:
...
// When found according element:
NSString *str;
GraphPoint *aPoint = [[GraphPoint alloc] init];
TBXMLElement *item = [TBXML childElementNamed:kX_Item parentElement:pntItem];
str = [TBXML textForElement:item];
aPoint.x = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
item = [TBXML childElementNamed:kY_Item parentElement:pntItem];
str = [TBXML textForElement:item];
aPoint.y = [str stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[points addObject:aPoint];
[aPoint release];
Leaks instrument shows leak in TBXML's textForElement function, which provides autoreleased string:
+ (NSString*) textForElement:(TBXMLElement*)aXMLElement {
if (nil == aXMLElement->text) return #"";
return [NSString stringWithCString:&aXMLElement->text[0] encoding:NSUTF8StringEncoding];
}
Since we're talking sometimes about hundreds or even thousands of points, these leaks become something huge. I cannot understand why autoreleased string produce leaks?
Any thoughts?
Thanks
There are no retain/release problems in the posted code. The only allocation in textForElement: is NSString's stringWithCString:encoding:, which I doubt to leak.
The problem is elsewhere and cannot be answered with the given information. Are you sure that you are reading Instruments results correctly? What does static analysis say about the code?
I don't know about the TBXML library, but the line containing nil == aXMLElement->text makes it look a bit fishy. It's not an error but a question of style: aXMLElement->text seems to be a C string while nil is used for objc objects.