Setting a weak ivar and getting nil - cocoa

In an ARC project, I'm setting a __weak ivar (declared as id __weak weakLayer;) to a CALayer which is retained by it's superlayer. Most times this works. Sometimes, the weak ivar tests as nil. I wrote some test code to simplify debugging that demonstrates the problem. I expect this to be an infinite loop but it breaks out, usually in less than 20 iterations. The number of iterations required to break out is not consistent.
array = [NSMutableArray array];
while (1) {
CALayer *layer = [CALayer layer];
[array addObject:layer];
weakLayer = layer;
if (!weakLayer) {
NSLog (#"nil");
break;
}
NSLog(#"not nil");
}
If I drop a breakpoint with an action of po weakLayer inside the if, it prints a valid object. I have tested on Xcode 4.3.3 and 4.4 with the Lion and Mountain Lion SDKs.

3 people (including myself) have now confirmed this to work on 10.8, but not on 10.7.4.

Related

Layer backed NSView problems in viewDidLoad

I set off to transform an NSImageView. My initially attempt was
self.imageView.wantsLayer = YES;
self.imageView.layer.transform = CATransform3DMakeRotation(1,1,1,1);
Unfortunately I noticed that the transform only happens sometimes (maybe once every 5 runs). Adding an NSLog between confirmed that on some runs self.imageView.layer is null. State of the whole project is shown on the image below.
It's an incredibly simple 200x200 NSImageView with an outlet to a generated NSViewController. Some experimentation showed settings wantsDisplay doesn't fix the problem, but putting the transform on an NSTimer makes it work every-time. I'd love an explanation why this happens (I presume it's due to some race condition).
I'm using Xcode 8 on the macOS 10.12 but I doubt this is the cause of the problem.
Update
Removing wantsLayer and madly enabling Core Animation Layers in Interface Builder did not fix the problem.
Neither did attempts to animate it (I wasn't sure what I was hoping for)
// Sometimes works.. doesn't animate
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
context.duration = 1;
self.imageView.animator.layer.transform = CATransform3DMakeRotation(1,1,1,1);
} completionHandler:^{
NSLog(#"Done");
}];
or
// Animates but only sometimes
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
animation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(1,1,1,1)];
animation.duration = 1;
[self.imageView.layer addAnimation:animation forKey:nil];
After experimenting with allowsImplicitAnimation I realised I might be trying to animate too early.
Moving the transform code into viewDidAppear made it work every time.
- (void)viewDidAppear {
[super viewDidAppear];
self.imageView.animator.layer.transform = CATransform3DMakeRotation(1,1,1,1);
}

When to retain and release CGLayerRef?

I have a question similar to this one:
CGLayerRef in NSValue - when to call retain() or release()?
I am drawing 24 circles as radial gradients in a view. To speed it up I am drawing the gradient into a layer and then drawing the layer 24 times. This worked really well to speed up the rendering. On subsequent drawRect calls some of the circles may need to be redrawn with a different hue, while others remain the same.
Every time through drawRect I recalculate a new gradient with the new hue and draw it into a new layer. I then loop through the circles, drawing them with the original layer/gradient or new layer/gradient as appropriate. I have a 24 element NSMutableArray that stores a CGLayerRef for each circle.
I think this is the answer provided in the question I linked above, however it is not working for me. The second time through drawRect, any circle that is drawn using the CGLayerRef that was stored in the array causes the program to crash when calling CGContextDrawLayerAtPoint. In the debugger I have verified that the actual hex value of the original CGLayerRef is stored properly into the array, and in the second time through drawRect that the same hex value is passed to CGContextDrawLayerAtPoint.
Further, I find that if I don't CGLayerRelease the layer then the program doesn't crash, it works fine. This tells me that something is going wrong with the memory management of the layer. It's my understanding that storing an object into an NSArray will increment it's reference count, and it won't be deallocated until the array releases it.
Anyway, here is the relevant code from drawRect. Down at the bottom you can see that I commented out CGLayerRelease. In this configuration the app doesn't crash although I think this is a resource leak. If I uncomment that release then the app crashes the second time though drawRect (between the first and second calls one of the circles has it's led_info.selected property cleared, indicating that it should use the saved layer rather than the new layer:
NSLog(#"ledView drawing hue=%4f sat=%4f num=%d size=%d",hue_slider_value,sat_slider_value,self.num_leds,self.led_size);
rgb_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:1.0];
end_color = [UIColor colorWithHue:1.0 saturation:1.0 brightness:1.0 alpha:0.0];
NSArray *colors = [NSArray arrayWithObjects:
(id)rgb_color.CGColor, (id)end_color.CGColor, nil];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,(__bridge CFArrayRef) colors, NULL);
CGLayerRef layer = CGLayerCreateWithContext(context, (CGSize){self.led_size,self.led_size}, /*auxiliaryInfo*/ NULL);
if (layer) {
CGContextRef layer_context = CGLayerGetContext(layer);
CGContextDrawRadialGradient(layer_context, gradient, led_ctr,self.led_size/8,led_ctr, self.led_size/2,kCGGradientDrawsBeforeStartLocation);
} else {
NSLog(#"didn't get a layer");
}
for (int led=0;led<[self.led_info_array count];led++) {
led_info=[self.led_info_array objectAtIndex:led];
// the first time through selected=1 and led_info.cg_layer=nil for all circles,
// so this branch is taken.
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
led_info.cg_layer=layer;
// the second time through drawRect one or more circles have been deselected.
// They take this path through the if/else
} else {
CGPoint startPoint=led_info.rect.origin;
// app crashes on this call to CGContextDrawLayerAtPoint
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
}
// with this commented out the app doesn't crash.
//CGLayerRelease(layer);
Here is the declaration of led_info:
#interface ledInfo : NSObject
#property CGFloat hue;
#property CGFloat saturation;
#property CGFloat brightness;
#property int selected;
#property CGRect rect;
#property CGPoint center;
#property unsigned index;
#property CGLayerRef cg_layer;
- (NSString *)description;
#end
led_info_array is the NSMutableArray of ledInfo objects, the array itself is a property of the view:
#interface ledView : UIView
#property float hue_slider_value;
#property float sat_slider_value;
#property unsigned num_leds;
#property unsigned led_size;
#property unsigned init_has_been_done;
#property NSMutableArray *led_info_array;
//#property layerPool *layer_pool;
#end
The array is initialized like this:
self.led_info_array = [[NSMutableArray alloc] init];
Edit: since I posted I have found that if I put retain/release around the assignemt into the NSMutableArray then I can also leave in the original CGLayerRelease and the app works. So I guess this is how it is supposed to work, although I'd like to know why the retain/release is necessary. In the objective C book I am reading (and the answer to the question linked above) I thought assigning into NSArray implicitly did retain/release. The new working code looks like this:
if (led_info.selected || led_info.cg_layer==nil) {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, layer);
CGContextAddRect(context, led_info.rect);
if (led_info.cg_layer) CGLayerRelease(led_info.cg_layer);
led_info.cg_layer=layer;
CGLayerRetain(layer);
} else {
CGPoint startPoint=led_info.rect.origin;
CGContextDrawLayerAtPoint(context, startPoint, led_info.cg_layer);
}
You can probably tell that I'm brand new to Objective C and iOS programming, and I realize that I'm not really sticking to convention regarding case and probably other things. I'll clean that up but right now I want to solve this memory management problem.
Rob, thanks for the help. I could use a little further clarification. I think from what you are saying that there are two problems:
1) Reference counting doesn't work with CGLayerRef. OK, but it would be nice to know that while writing code rather than after debugging. What is my indication that when using "things" in Objective C/cocoa that resource counting doesn't work?
2) You say that I'm storing to a property, not an NSArray. True, but the destination of the store is the NSArray via the property, which is a pointer. The value does make it into the array and back out. Does resource counting not work like this? ie instead of CGLayerRef, if I were storing some NSObject into NSArray using the code above would resource counting work? If not, then would getting rid of the intermediate led_info property and accessing the array directly from within the loop work?
You're not storing the layer directly in an NSArray. You're storing it in a property of your ledInfo object.
The problem is that a CGLayer is not really an Objective-C object, so neither ARC nor the compiler-generated (“synthesized”) property setter will take care of retaining and releasing it for you. Suppose you do this:
CGLayerRef layer = CGLayerCreateWithContext(...);
led_info.cg_layer = layer;
CGLayerRelease(layer);
The cg_layer setter method generated by the compiler just stores the pointer in an instance variable and nothing else, because CGLayerRef isn't an Objective-C object reference. So when you then release the layer, its reference count goes to zero and it's deallocated. Now you have a dangling pointer in your cg_layer property, and when you use it later you crash.
The fix is to write the setter manually, like this:
- (void)setCg_layer:(CGLayerRef)layer {
CGLayerRetain(layer);
CGLayerRelease(_cg_layer);
_cg_layer = layer;
}
Note that it's important to retain the new value before releasing the old one. If you release the old one before retaining the new one, and the new one happens to be the same as the old one, you might deallocate the layer right in the middle!
UPDATE
In response to your edits:
Reference counting works with CGLayerRef. Automatic reference counting (ARC) doesn't. ARC only works with things that it thinks are Objective-C objects ARC does not automatically retain and release a CGLayerRef, because ARC doesn't think a CGLayerRef is a reference to an Objective-C object. An Objective-C object is (generally speaking) an instance of a class declared with #interface, or a block.
The CGLayer Reference says that CGLayer is derived from CFType, the basic type for all Core Foundation objects. (As far as ARC is concerned, a Core Foundation object is not an Objective-C object.) You need to read about “Ownership Policy” and “ Core Foundation Object Lifecycle Management” in the Memory Management Programming Guide for Core Foundation.
The “destination of the store” is an instance variable in your ledInfo object. It's not “the NSArray via the property”. The value doesn't ”make it into the array and back out.” The array gets a pointer to your ledInfo object. The array retains and releases the ledInfo object. The array never sees or does anything with the CGLayerRef. Your ledInfo object is responsible for retaining and releasing any Core Foundation objects it wants to own, like the layer in its cg_layer property.
As I mentioned, if ledInfo doesn't retain the layer (with CFRetain or CGLayerRetain) in its cg_layer setter, it risks the layer being deallocated, leaving the ledInfo with a dangling pointer. Do you understand what a dangling pointer is?

Using delegates in OS X

I'm new at OS X development, I've been having a problem getting a delegate callback and I somehow suspect that it might be a memory problem. I have an NSViewController. In it's init method I am setting up a custom NSObject as so:
MyObject *aManager = [[MyObject alloc] initManager];
__theManager = aManager;
self.theManager.delegate = self;
[aManager release];
the delegate I've setup as nonatomic, assign. Looking at the breakpoints I should be seeing the callback in my view controller but this never happens. Any ideas?
__theManager = aManager; should be self.theManager = aManager;, assuming theManager is a retained property. The problem you have is that alloc] init]; gives aManager a retain count of +1. __theManager = aManager; does not increase that count, as the iVar is set directly. When you release it, the retain count becomes 0, and so it is deallocated.

How do you set a label text as something from an NSMutableArray?

I am trying to understand how to set a label to be the text from an array when you press the button. When I press the button, the label disappears, and then nothing comes up. No crashes in code.
Relevant code:
-(void)setupArray {
wordArray = [[NSMutableArray alloc] init];
[wordArray addObject:#"test1"];
[wordArray addObject:#"test2"];
[wordArray addObject:#"test3"];
}
- (IBAction)start:(id)sender {
int value = (arc4random() % 3) + 1;
self.typeThis.text = [self.wordArray objectAtIndex:value];
}
typeThis is the label name, and I think I have hooked up everything already, i.e. set up the buttons/delegates/etc...I don't understand why it isn't working. Can anybody help?
considering you have bound everything properly and you are not under ARC. Here is a thing that might cause you the issue.
when you are allocating wordArray you can try using following code snippet.
NSMutableArray tempArray = [[NSMutableArray alloc] init];
self.wordArray = tempArray;
[tempArray release];
if you are under ARC you can try self.wordArray = [NSMutableArray array];
then add objects to self.wordArray i.e.[self.wordArray addObject:#"test1"];. Here is some explanation about arc4random().
EDIT :
Here's a public spec for Automatic Reference Counting and a quote from the public iOS 5 page:
Automatic Reference Counting (ARC) for Objective-C makes memory
management the job of the compiler. By enabling ARC with the new Apple
LLVM compiler, you will never need to type retain or release again,
dramatically simplifying the development process, while reducing
crashes and memory leaks. The compiler has a complete understanding of
your objects, and releases each object the instant it is no longer
used, so apps run as fast as ever, with predictable, smooth
performance.
It is possible to detect if ARC is enabled. Simply add the following snippet to any file that requires ARC.
#ifndef __has_feature
#define __has_feature(x) 0 /* for non-clang compilers */
#endif
#if !__has_feature(objc_arc)
#error ARC must be enabled!
#endif
More info :
http://clang.llvm.org/docs/LanguageExtensions.html#__has_feature_extension
HTH.
Your '+1' is giving you a result between 1 and 3, and your indexes are from 0 to 2, so I'd expect it to go wrong one time in 3.
Is this under ARC? If so, is wordArray declare as strong?

NSScreen leaking under GarbageCollection, Is this a bug

I've been trying to debug memory leaks in an objective-c program for the last couple of days and was playing around with some very basic sample code to test some ideas and came across something that seems to be a bug to me but wanted to get some opinions from those with more knowledge when it comes to developing in the Mac world.
I have the following bit of code:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRect desktopRect = NSZeroRect;
NSArray* screens = [[NSScreen screens] autorelease];
int x = 0;
for (x = 0; x < [screens count]; x++)
{
NSScreen *screen = [screens objectAtIndex:x];
}
[pool drain];
This code lives inside a method that I am calling from a while loop that continues until I kill the process. (Remember this is just a simple test.)
When I run this loop and watch allocations in Instruments things work perfectly fine when I am not implementing Garbage Collection. My overall number of allocations stays constant and everything with the NSScreens are getting cleaned up and released.
As soon as I enable Garbage Collection, the call to [NSScreen screens] starts leaking core graphics objects like mad by never releasing them.
I have seen in other forums where people a few years back talked about Core Graphics being very leaky under Garbage Collection. I'm wondering if that is the case here as well.
Any thoughts from the community?
Ok, I'm going to go out on a limb here and say that yes it is a bug on Apples part though I did find a solution using some Toll-Free-Bridging magic. I modified the code to be:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRect desktopRect = NSZeroRect;
CFArrayRef screens = CFMakeCollectable((CFArrayRef)[NSScreen screens]);
int x = 0;
for (x = 0; x < [screens count]; x++)
{
NSScreen *screen = (NSScreen*)CFArrayGetValueAtIndex(screens,x);
}
[pool drain];
By calling CFMakeCollectable on the toll-free-bridged array, it allowed the background core graphics objects to be properly cleaned up when using garbage collection.
This definitely makes me think that Apple needs to do some more work with Garbage Collection and Cocoa objects that wrap Core Graphics.

Resources