Reputation: 5960
I don't understand this one unless it's because I'm releasing the property instead of the ivar. Can someone shed light on the problem?
self.dataToBeLoaded = [[NSMutableData alloc] initWithLength:10000];
[self.dataToBeLoaded release];
The warning is Incorrect decrement of the reference count of an object that is not owned by the caller
.
The dataToBeLoaded
property has the retain attribute associated with its setter.
My understanding is the the alloc init increments the retain count and the property assignment increments the retain count. Since I only one to retain it once, that's why I release it immediately after the assignment.
UPDATE -- some experimental results:
Since I noted in my comments below that I have received contradictory advice on what the retain property does to the synthesized setter, I thought I would do a little experiment using the code above, modified with some logging:
NSLog(@"retain 1 = %d", [dataToBeLoaded_ retainCount]);
self.dataToBeLoaded = [[NSMutableData alloc] initWithLength:10000];
NSLog(@"retain 2 = %d", [dataToBeLoaded_ retainCount]);
[self.dataToBeLoaded release];
NSLog(@"retain 3 = %d", [dataToBeLoaded_ retainCount]);
The results at each log statement were 0, 2, and 1.
Apparently, it's not possible to step into the alloc or the init code to see the retain count go from 0 to 1 to 2. I could have subclassed the NSMutableData class, but I was short on time.
I know a lot is said that you can't rely on the value of the retainCount property, but what I have seems consistent and I would expect reasonable behavior over the short scope of the code like that shown in the example. So I'm inclined to believe that prior advice is correct -- the retain property is a promise to include a retain within the setter. So here I have the retain from the alloc/init and the retain from the call to the setter. Hence, the retain count is set to 2.
When I run this code:
NSMutableData *theData;
NSLog(@"retain 1 = %d", [theData retainCount]);
theData= [[NSMutableData alloc] initWithLength:10000];
NSLog(@"retain 1a = %d", [theData retainCount]);
self.dataToBeLoaded = theData;
NSLog(@"retain 2 = %d", [theData retainCount]);
[self.dataToBeLoaded release];
NSLog(@"retain 3 = %d", [theData retainCount]);
The retain count at each log statement is 0, 1, 2, 1.
So I have evidence that suggests the setter is providing a retain
. This appear to be more of a promise than a hint, because it is actually happening.
I'm open to other explanations. I don't want to be arrogant about this. I just want to get it right as to what is happening. I appears that the warning (in the subject of this question) is really spurious and not something to worry about.
One more experiment is done using assign
rather than retain
as an attribute in the @property statement. With the same code:
NSMutableData *theData;
NSLog(@"retain 1 = %d", [theData retainCount]);
theData= [[NSMutableData alloc] initWithLength:10000];
NSLog(@"retain 1a = %d", [theData retainCount]);
self.dataToBeLoaded = theData;
NSLog(@"retain 2 = %d", [theData retainCount]);
[self.dataToBeLoaded release];
NSLog(@"retain 3 = %d", [theData retainCount]);
The retain count at each log is 0, 1, 1 (the setter did not retain), then the error message: message sent to deallocated instance
. The last release had set the retain count to zero, which triggered the deallocation.
UPDATE 2
A final update -- when the synthesized setter is overridden with your own code, the retain attribute is no longer observed unless your setter explicitly includes it. Apparently (and this contradicts what I had been told in other threads here) you have to include your own retain in the setter if that's what you want. While I didn't test it here, you probably need to release the old instance first, or it will be leaked.
This custom setter no longer has the property attributes of the @propety declaration:
- (void) setDataToBeLoaded:(NSMutableData *)dataToBeLoaded {
dataToBeLoaded_ = dataToBeLoaded;
}
This makes sense. Override a synthesized setter and you override all of the declared properties. Use a synthesized setter, and the declared properties are observed in the synthesized implementation.
The @property attributes represent a "promise" as to how the synthesized setter is implemented. Once you write a custom setter, you're on your own.
Upvotes: 4
Views: 5096
Reputation: 5960
I provided a couple of updates that I think answer what is going on here. With some test results, my conclusion is that this warning is spurious, meaning it does not really identify improper code. The updates should speak for themselves. They are given above.
Upvotes: 0
Reputation: 38728
My guess would be that the method
- (NSMutableData *)dataToBeLoaded;
does not contain any of the memory management keywords therefore it is assumed that you do not own the data returned and therefore should not be releasing it.
Either use
NSMutableData *data = [[NSMutableData alloc] initWithLength:1000];
self.dataToBeLoaded = data;
[data release]; data = nil;
or if you can why not lazy load it when you actually need it?
- (NSMutableData *)dataToBeLoaded;
{
if (!_dataToBeLoaded) {
_dataToBeLoaded = [[NSMutableData alloc] initWithLength:1000];
}
return _dataToBeLoaded;
}
Upvotes: 2
Reputation: 299265
The key is to think through what the below code is doing. I'll write it out in full for clarity:
[self setDataToBeLoaded:[[NSMutableData alloc] initWithLength:10000]];
This creates an object with a +1 retain count and passes it to setDataToBeLoaded:
. (*) It then throws away its reference to that object, leaking it.
[[self dataToBeLoaded] release];
This calls dataToBeLoaded
and releases the object returned. There is no promise whatsoever that the object returned by dataToBeLoaded
is the same as the object passed to setDataToBeLoaded:
. You probably think they're the same, and looking at your code you can probably convince yourself that it will always work out that way, but that's not an API promise.
The code posted by Antwan is correct:
NSMutableData *data = [[NSMutableData alloc] initWithLength:1000];
self.dataToBeLoaded = data;
[data release];
That creates an object with a +1 retain count. Then passes it to a method, then releases it.
Or, if you're willing to use the autorelease pool, you can simplify it to:
self.dataToBeLoaded = [NSMutableData dataWithLength:1000];
(*) Technically this passes a message to self
that may or may not cause this method to be called, but that muddies the issue. For most purposes, pretend it's a method call. But do not pretend that it just sets the property. It really is going to call some method.
EDIT:
Maybe this code will make the issue a little clearer. It's indicative of common caching solutions:
.h
@interface MYObject : NSObject
@property (nonatomic, readwrite, strong) NSString *stuff;
@end
.m
@interface MYObject ()
@property (nonatomic, readwrite, weak) MYStuffManager *manager;
@implementation MYObject
... Initialize manager ...
- (NSString*)stuff {
return [self.manager stuffForObject:self];
}
- (void)setStuff:(NSString *)stuff {
[self.manager setStuff:stuff forObject:self];
}
Now maybe manager
does some foolery in the background. Maybe it caches various copies of stuff
. Maybe it copies them. Maybe it wraps them into other objects. What's important is that you can't rely on -stuff
always returning the same object you passed to -setStuff:
. So you certainly shouldn't release it.
Note that nothing in the header indicates this, and nothing should. It's not the callers' business. But if the caller releases the result of -stuff
, then you will get hard-to-debug crashes.
@synthesize
is just a shorthand for writing some tedious code (code that implements stuff
and setStuff:
as reading and writing an ivar). But nothing says that you have to use @synthesize
for your properties.
Upvotes: 4
Reputation: 6991
It just means that you are releasing an object you don't own.
I'd say call it with the instance var directly instead of using the getter, but not sure whether that will fix your analyses warnings. Also why not use [NSMutableData dataWithLength:1000]; which is autoreleased and therefore eliminates the need of that extra release call ( and would probably get rid of that warning too! )
other ways you could fix it:
NSMutableData *data = [[NSMutableData alloc] initWithLength:1000];
self.databToBeLoaded = data;
[data release];
Upvotes: 1