I seem to be retaining self inside a block even when using an indirect __block reference to it, perhaps cause of copying the block to the heap? - cocoa-touch

[EDIT: Since it was causing confusion, this whole case assumes MRR and not ARC]
I'm having an odd (there's an explanation obviously, I just can't figure it out) behavior with a block which references self (indirectly) and is in turned copied to another object's property (that is, copied from object As' stack to the heap and retained by an object B). If the block doesn't contain the reference to _this, Object A's dealloc is called every time it is popped from a navigation controller, as it should. However, if the block references _this, the object's (MyObjectA in the code below) dealloc is only called every other time. That is, I push this view controller subclass, createBlock is called, I pop and nothing happens. I push again, createBlock is called again, then pop and then it DOES call dealloc on MyObjectA.
For the sake of brevity, I'm only posting the snippets I believe are key to the behavior.
Say I have an object MyObjectA (sublcass of a custom UIViewController), which includes a method createBlock, like so:
- (void)createBlock
{
__block MyObjectA* _this = self;
int(^animationBlock)(NSArray*,NSDictionary*);
animationBlock =
^(NSArray* layers, NSDictionary* parameters)
{
[CATransaction begin];
[CATransaction setCompletionBlock:
^{
for(CALayer* layer in layers)
layer.opacity = 1;
}];
CABasicAnimation* a2 = [CABasicAnimation animationWithKeyPath:#"opacity"];
a2.fromValue = [NSNumber numberWithFloat:0.];
a2.toValue = [NSNumber numberWithFloat:1.];
a2.duration = .4;
a2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
a2.fillMode = kCAFillModeBoth;
a2.removedOnCompletion = NO;
CABasicAnimation* a = [CABasicAnimation animationWithKeyPath:#"position.x"];
a.duration = .4;
a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
a.fillMode = kCAFillModeBoth;
a.removedOnCompletion = NO;
CAAnimationGroup* g = [CAAnimationGroup animation];
g.animations = [NSArray arrayWithObjects:a,a2, nil];
g.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
g.fillMode = kCAFillModeBoth;
g.removedOnCompletion = NO;
CALayer* numberLayer;
CALayer* flechaLayer;
CGFloat timeOffset = 0;
for(int i = 0; i < [layers count]; i+=2)
{
numberLayer = [layers objectAtIndex:i];
flechaLayer = [layers objectAtIndex:i+1];
a2.beginTime = [_this.view.layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset;
[numberLayer addAnimation:a2 forKey:nil];
a2.beginTime = 0;
a.fromValue = [NSNumber numberWithFloat:flechaLayer.frame.origin.x + 100];
a.toValue = [NSNumber numberWithFloat:flechaLayer.frame.origin.x + flechaLayer.frame.size.width / 2.];
g.duration = 3;
g.beginTime = [_this.view.layer convertTime:CACurrentMediaTime() fromLayer:nil] + timeOffset + .4;
[flechaLayer addAnimation:g forKey:nil];
timeOffset += 1.5;
}
[CATransaction commit];
return 0;
};
[[AnimationFactory sharedFactory] registerAnimationBlock:animationBlock forKey:#"EnsureFlechasNutricion"];
}
As you can see, there's a reference to _this in the animation block.
Then, the AnimationFactory's (a singleton) method which registers the blocks is:
- (void)registerAnimationBlock:(int(^)(NSArray*, NSDictionary*))animationBlock forKey:(NSString*)key
{
int(^heapBlock)(NSArray*, NSDictionary*) = [animationBlock copy];
[self.animationBlocks setObject:heapBlock forKey:key];
[heapBlock release];
}
My guess is that copying the block to the heap is retaining MyObjectA, or perhaps adding the block to the NSMutableDictionary in the AnimationFactory.. but I'm not sure.
Any thoughts?

Ok I figured it out: When I add the newly copied (heap) block to the AnimationFactory's dictionary, I'm necessarily retaining self even if doing the weak reference shebang when originally declaring the block in self.
The solution is to get a weak (aka __block Class* identifier = eval since I'm not on ARC) reference to self.view, which is the reason I was referencing self to start with, as opposed to one to self. This way, in spite of this view's reference count increasing, self's ref count stays correct. Then, on pop, self is not retained by the AnimationFactory's dictionary and does call dealloc.
I should mention that self's dealloc includes a call to another method which in turn removes all registered blocks from the AnimationFactory, bringing self.view's retain count back to normal too implicitly, and thus not leaking.

[Update: this answer applies when using ARC and from the comments it turns out MRC is being used, so its not the answer!]
The __block attribute is for when you need a variable which can be updated by a block, i.e. the variable is passed to the block by reference rather than by value as is the default case. This does not appear to be needed in your code, you don't update the value of _this within your block.
To break a strong reference cycle use the __weak attribute. Your current _this is a strong reference to the same object that self references, so your block ends up with a strong reference.

Related

Why the local variables are always strong?

According to the notes in class, it is said that the local variables are always strong. I really wonder the reason for it. Why the local variables are always strong.
BTW, I am learning blocks. As far as I know, the local variables is supposed to be used and declared inside the method or blocks. However, the local variables I see when using blocks are declared outside the method, but used inside the block. Therefore, I am really curious about it.
The code where the local variable is outside the method looks like this:
_block BOOL stoppedEarly = NO; // this is the local variable
double stoppedValue = 52;
[aDictionary enumerateKeysAndObjectsUsingBlock: ^(id key, id value, BOOL *stop) {
NSLog (#"value for key %# is %#", key, value);
if ([#"ENOUGH" isEqualToString: key] || [value doubleValue] == stoppedValue) {
*stop = YES;
stoppedEarly = YES;
}
}];
1) it just makes sense for local variables to be strong, otherwise all objects would be released right after the creation or you would have to add the __strong attribute everytime yourself:
UIView *view = [[UIView alloc] init];
//if it wasn't a strong reference, view would be released here
//and you couldn't use the reference in the next line
view.backgroundColor = [UIColor blackColor];
2) the Block in your example gets called once for every key-value-pair in the dictionary. Since the intention of the code is to use one variable for all blockcalls, it is declared outside of it. Think of global variables and methods.
Edit: here are some basic examples on scopes of variables.
//global, visible in all methods
NSInteger globalVariable;
- (void)someMethod{
//local, visible only until the end of the method
NSInteger localMethodVariable;
void (^block)() = ^void() {
//local, visible only until the end of the block
NSInteger localBlockVariable;
//may use all 3 variables here
};
//may use globalVariable and localMethodVariable here
}
- (void)someOtherMethod{
//may use globalVariable here
}

Objective-c pointer

I have a property :
#property(nonatomic, assign)UIView *currentView;
when I process the follow code, why it will break?
_currentView =nil;
UIView * v1 = [[UIView alloc] initWithFrame:CGRectZero];
_currentView = v1;
NSLog(#"_currentView %#", _currentView);
NSLog(#"v1 %#", v1);
[v1 release];
NSLog(#"_currentView %#", _currentView); ///break here.
NSLog(#"v1 %#", v1);
I think the _currentView and v1 both point to a same memory. When use v1 to realese the object, and use _currentView to print the object, it will crash. I can understand this.
But if the add follow line after v1 release and before print _currentView. I can`t understand the log.
v1 = nil;
the code like follow
_currentView =nil;
UIView * v1 = [[UIView alloc] initWithFrame:CGRectZero];
_currentView = v1;
NSLog(#"_currentView %#", _currentView);
NSLog(#"v1 %#", v1);
[v1 release];
v1 = nil;
NSLog(#"_currentView %#", _currentView);
NSLog(#"v1 %#", v1);
print result is :
> 2012-05-30 15:16:57.314 All[3068:15203] _currentView <UIView:
0x81ccbc0; frame = (0 0; 0 0); layer = <CALayer: 0xa07e5a0>>
> 2012-05-30 15:16:57.798 All[3068:15203] v1 <UIView: 0x81ccbc0; frame =
(0 0; 0 0); layer = <CALayer: 0xa07e5a0>
> 2012-05-30 15:16:59.189 All[3068:15203] _currentView <UIView: 0x81ccbc0; frame = (0 0; 0 0); transform = [0, 0, 0, 0, 0, 0]; alpha = 0; layer = (null)
> 2012-05-30 15:17:09.042 All[3068:15203] v1 (null)
Why after invoke v1 release, and log _currentView, it will print
_currentView <UIView: 0x81ccbc0; frame = (0 0; 0 0);
transform = [0, 0, 0, 0, 0, 0]; alpha = 0; layer = (null)>
This is not necessarily related to the #property attribute (assign or retain) because you are not using accessors
This is what happens in your code:
#property(nonatomic, assign)UIView *currentView;
You declare an ivar to be assign although that is irrelevant in this case since you are not using self.currentView or [self setCurrentView:...];.
_currentView = nil;
// You just made your pointer _currentView point to nil or 0
UIView *v1 = [[UIView alloc] initWithFrame:CGRectZero];
// You created an UIView object and made v1 to point to it. (v1 is NOT the real object)
_currentView = v1;
// You just made _currentView to point the same object v1 points to
NSLog(#"_currentView %#", _currentView);
// and because of that you correctly see the object here (because _currentView points to the view object)
NSLog(#"v1 %#", v1);
// also here (because v1 points to the object from the start)
[v1 release];
// now you release the object pointed by v1 , since no other is retaining it, it gets deallocated BUT note that v1 is still pointing to it, which now is garbage memory!)
//NSLog(#"_currentView %# v1 %#", _currentView, v1);
// If above line were executed the app will crash because of v1 and _currentView both are pointing to the object that was just released and it is not valid anylonger.
v1 = nil;
// Now you made v1 to point to nothing so next time you use it terrible things will not happen (★) :)
NSLog(#"_currentView %#", _currentView);
// Oh no! you called _currentView and since it was still pointing to the object you released a bit ago the app crashes :(
NSLog(#"v1 %#", v1);
// This is fine, you set v1 to point to nil so it is not pointing to some garbage memory you simply get nil.
(★) Because in objective-c sending methods to nil is harmless, using nil as parameters of other methods is another story
Another thing:
Even if you write self.currentView = v1; instead of _currentView = v1; results would be the same since the properly is declared as assign.
Things would be different if you declare the property as retain. In that case after you do [v1 release]; the object will not be deallocated since the object was retained by currentView (self.currentView = v1). Then if you do v1 = nil v1 will be pointing to nil and the object will be reachable only by currentView.
Then if you do _currentView = nil then _currentView will be pointing to nil but the object itself will not be released since you didn't use the accessory method (nor explicitly released) hence you will get a dangling pointer.
Not all the times properties declared as retain are the solution, it is case by case. I recommend to read a bit more about memory management in Obj-c (at least this) also a bit about C pointers and then about ARC
The reason why you get different printouts for second output is following:
After you have executed: [v1 release]; both v1 and _currentView are pointing to old block of memory. However setting v1 = nil; will set only v1 to nill and not _currentView (remember these are pointers).
I hope this clarifies the things for you.
Kind regards,
Bo
The problem is how you declared the property:
#property(nonatomic, assign)UIView *currentView;
It should be:
#property(nonatomic, retain)UIView *currentView;
When you try to NSLog, it will have a garbage valor, since you release it previously:
[v1 release];
NSLog(#"_currentView %#", _currentView);
Remember that, at this point, when you try to NSLog it, v1 and _currentView will be pointing to the same block of memory.
Just want to add one point to nacho4d's answer. If you NSLog a deallocated object, sometimes it will crash and sometimes it won't. When the object is deallocated, all that happens is it gets added back on to the list of free memory blocks. The actual content of the memory still looks like an object and until some or all of the block gets reused, sending messages to it can often work.
One of three things can happen when you NSLog a deallocated object.
It can log as if it were still alive
A completely different object might respond if it starts exactly at the same place
You get EXC_BAD_ACCESS
Whichever one happens is largely a matter of chance.
You are declaring a property, but you are not using it, you are using the instance variable directly. Also you are failing to retain the memory that your variable is pointing to.
It looks like you have an instance variable declared in your class:
#interface MyClass : NSObject {
UIView * _currentView;
}
#end
What you are doing is that you are accessing this directly, without using the property. What happens is that you are not retaining the memory when you assign it, which means you are releasing it completely and it gets deleted. To make it work this way, you could do this:
UIView * v1 = [[UIView alloc] initWithFrame:CGRectZero];
[_currentView release];
_currentView = [v1 retain]; // <-- OBSERVE the retain
NSLog(#"_currentView %#", _currentView);
NSLog(#"v1 %#", v1);
[v1 release];
NSLog(#"_currentView %#", _currentView); // Should not break anymore
NSLog(#"v1 %#", v1);
Later you need to release the object retained in _currentView:
-(void)dealloc {
[_currentView release];
}
(Observe that you also need to do this if you want to assign a new value to _currentView)
Another way would be to actually use the property that you declared, but instead use a retain property:
#property(nonatomic, retain)UIView *currentView;
To make it accessible, you need to synthesize it in your class implementation:
#implementation MyClass
#synthesize currentView = _currentView;
/*...*/
#end
That will make the retain be handled for you. Also you do not need to think about releasing the previous value stored in the variable, since that will be handled for you. However you need to access the property this way:
self.currentView
Your code example would look like this:
UIView * v1 = [[UIView alloc] initWithFrame:CGRectZero];
self.currentView = v1;
NSLog(#"currentView %#", self.currentView);
NSLog(#"_currentView %#", _currentView);
NSLog(#"v1 %#", v1);
[v1 release];
NSLog(#"currentView %#", self.currentView);
NSLog(#"_currentView %#", _currentView); ///Should not break anymore
NSLog(#"v1 %#", v1);
As you would see from the printout, the two variables still point to the same memory. The different is that since you are retaining the memory, a retain counter is added to it. The memory will not be freed until the retain counter has reached 0, which means you need to release it one time for every time you retain it. Also in the later case you need to release the retain in your dealloc method:
-(void)dealloc {
[_currentView release];
}
As for you last question this row
v1 = nil;
will only affect the address to which v1 points to. It would not affect the variable _currentView nor the memory it is pointing to.

UIImage converts to NSData mysteriously

Please take a look at these two simple pieces of code. This
- (void)testMethod
{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myEncodedObjectKey"];
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
for (int i = 0; i < self.avatar.count; i++)
[self.avatar replaceObjectAtIndex:i withObject:[UIImage imageWithData:[self.avatar objectAtIndex:i]]];
if ([[self.avatar objectAtIndex:0] isKindOfClass:[UIImage class]])
NSLog(#"UIImage");//at this moment it's UIImage
}
and this:
[currentProfile testMethod];
if ([[currentProfile.avatar objectAtIndex:0] isKindOfClass:[NSData class]])
NSLog(#"NSData");//Moment later it is NSData
In the first one i fetch a custom object from the NSUserDefaults and work with a NSMutableArray variable named "avatar". I convert its each object from NSData to UIImage. Then i check what i've got by using NSLog . It's UIImage. At the second piece of code you can see how a moment later what was UIImage turns back to NSData by its own will. Seems like i described my issue clearly. Do you understand what's going on? I don't. Thanks a lot in advance for your attention
Why are you changing the self object in your -testMethod method? This is highly illegal.
What you're actually doing is setting a local variable self, which is passed as a parameter to your method, to a new value. This means you're not editing the receiver of the method, you're just editing your parameter.
When your method is called at runtime the C function objc_msgSend() is called:
// Declaration of objc_msgSend
id objc_msgSend(id receiver, SEL selector, ...);
Now when you call your method ...
[myInst testMethod];
... this is what actually gets called at runtime:
objc_msgSend(myInst, #selector(testMethod));
Do you already see what's happening? In your method implementation the self variable is set to the first argument of objc_msgSend. When you're reassigning self, your not editing what the variable myInst contains and thus you're not editing your the original instance you passed. You're just setting myInst, aka self, a local variable, to your knew pointer. The caller of the function will not notice the change.
Compare you're code to the following C code:
void myFunction(int a) {
a = 3;
}
int b = 2;
myFunction(b);
printf("%d\n", b);
// The variable b still has the original value assigned to it
The above code does the same you do:
// Variation on objc_msgSend
void myMethodWrittenInC(id myInst) {
// Local variable changes, but will not change in the calling code
myInst = nil;
}
MyClass *myObj;
myObj = [[MyClass alloc] init];
myMethodWrittinInC(myObj);
// At this point myObj is not nil
And finally this is what you do:
- (void)testMethod
{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myEncodedObjectKey"];
// You assign the local variable self (passed as an invisible argument
// to your method) to your new instance, but you do not edit the original
// instance self pointed to. The variable currentProfile does not change.
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
for (int i = 0; i < self.avatar.count; i++)
[self.avatar
replaceObjectAtIndex:i
withObject:[UIImage imageWithData:[self.avatar objectAtIndex:i]]];
if ([[self.avatar objectAtIndex:0] isKindOfClass:[UIImage class]])
NSLog(#"UIImage");//at this moment it's UIImage
}
// (1) Here currentProfile points to an instance of your class
[currentProfile testMethod];
// (2) it calls the method, but the local variable does not change
// and still points to the same instance.
if ([[currentProfile.avatar objectAtIndex:0] isKindOfClass:[NSData class]])
NSLog(#"NSData");//Moment later it is NSData

Message sent to deallocated instance -- short and simple

This has to be very basic, but I don't see the problem. The program crashes whenever the following code block is executed. Analyzer reports a possible memory leak:
if (anImage) {
eventImageView.frame = defaultEventImageFrame;
UIImage *scaledImage = [anImage scaleToFitWithin:defaultEventImageFrame.size interpolationQuality:kCGInterpolationHigh];
eventImageView.backgroundColor = [UIColor colorWithPatternImage:scaledImage];
}
The message is -[UIImage release]: message sent to deallocated instance 0x1129d920*
Instance 0x1129d920 is scaledImage
I tried adding retains and releases, like this
if (anImage) {
eventImageView.frame = defaultEventImageFrame;
UIImage *scaledImage = [[anImage scaleToFitWithin:defaultEventImageFrame.size interpolationQuality:kCGInterpolationHigh] retain];
eventImageView.backgroundColor = [UIColor colorWithPatternImage:scaledImage];
[scaledImage release];
}
and still get the error message.
So I tried replacing the assignment with a copy, like this
if (anImage) {
eventImageView.frame = defaultEventImageFrame;
UIImage *scaledImage = [anImage copy];
eventImageView.backgroundColor = [UIColor colorWithPatternImage:scaledImage];
}
And the problem is gone.
Checking the scaleToFitWithin method, I see it returns an autoreleased object:
- (UIImage *) scaleToFitWithin:(CGSize) newSize
interpolationQuality:(CGInterpolationQuality)quality{
CGSize originalImageSize = self.size;
CGSize newImageSize;
if (originalImageSize.width <= originalImageSize.height) {
newImageSize.width = self.size.width * newSize.width / self.size.width;
newImageSize.height = self.size.height * newSize.width / self.size.width;
}
else {
newImageSize.width = self.size.width * newSize.height / self.size.height;
newImageSize.height = self.size.height * newSize.height / self.size.height;
}
return [[[self normalize] resizedImage:newImageSize interpolationQuality:kCGInterpolationHigh] autorelease];
}
So there is something about memory management that I'm not understanding. What is the problem likely to be?
The problem is most likely that the scaleToFitWithin:interpolationQuality: method is calling autorelease on an object which has already previously been autoreleased. This can occur if you initialise the UIImage using a temporary constructor, like +[UIImage imageWith...], earlier in the same method you call the scaling method from. The reason it works when you use [anImage copy] is because the behaviour of the copy constructor is such that the object returned to you has already had retain called on it (so it has a local retain count of 1 and zero autoreleases).
What happens at the end of the current run loop is: the autorelease pool which is currently in use is drained, and as a part of that two release messages will be sent to the UIImage. When the first one is sent, the application then runs off and calls dealloc on the image, because the retainCount has decreased to zero. When the second one is sent, the application throws an exception because a message is being sent to an object which was previously deallocated.
Try removing the autorelease message from the scaleToFitWithin:interpolationQuality: method. Even if your resizedImage:interpolationQuality: method IS returning a new object, you should only be calling autorelease in that method rather than the scaling method.
It seems that the method resizedImage:interpolationQuality: itself returns an autoreleased object and you are again autoreleasing it in the reutun statement. Just remove the autorelease from the return statement,
return [[self normalize] resizedImage:newImageSize
interpolationQuality:kCGInterpolationHigh];
Then you don't have to retain/release or copy the returned object in if (anImage) {...} block.

Conditional alloc/dealloc? [Objective-c and Cocos2D]

What if I want to alloc a class inside another and I want to reference it easily, but sometimes this class would not need to be alloc'd, therefore not dealloc'd. How is this done? Can I put a conditional inside dealloc so it doesn't have to be released?
In more detail, I'm using Cocos2D. I have player ability classes that may or may not need to be allocated. In my init:
// Abilities
if(abilityRushH == 0){
objects = [theMap objectGroupNamed:#"oj"];
startPoint = [objects objectNamed:#"item_ability_rushH"];
x = [[startPoint valueForKey:#"x"] intValue];
y = [[startPoint valueForKey:#"y"] intValue];
rushH = [[RushHorizontal alloc] init];
[self addChild:rushH.rushHSpriteSheet];
rushH.rushHSprite.position = ccp(x,y);
}
if(abilityRushV == 0){
objects = [theMap objectGroupNamed:#"oj"];
startPoint = [objects objectNamed:#"item_ability_rushV"];
x = [[startPoint valueForKey:#"x"] intValue];
y = [[startPoint valueForKey:#"y"] intValue];
rushV = [[RushVertical alloc] init];
[self addChild:rushV.rushVSpriteSheet];
rushV.rushVSprite.position = ccp(x,y);
}
Cocos2D needs to keep the reference so it can scroll with the map. But if I'm not alloc'ing it, how do I NOT dealloc?
Since you are talking of releasing it in dealloc, there will be an instance variable for this. Now when any instance of an Objective-C class is allocated, all its objects are nil and c types are set to 0 (or equivalent values). So you don't need to put any extra effort if the object of your class isn't instantiated as the instance variable will be nil at dealloc and so release message sent to it will have no effect.
Make sure the optional variable is nil when its not needed, and do a nil check before dealloc'ing.