I have a Cocoa app with ARC enabled. I am using OS 10.8 and Xcode 4.4. The app sporadically crashes upon calls to CGLayerRelease, dumping messages like the following to console:
error for object 0x10a2d2000: entry for pointer being freed from death-row vanished
If I comment out the calls to CGLayerRelease the crashing stops. Do I need to call CGLayerRelease in ARC enabled projects? The documentation isn't very clear about this.
It is hard to post code for this because it is spread throughout a large file for a class that inherits from NSView. The class has a data member CGLayerRef layer;. Then inside the drawRect function:
if (layer == nil) {
layer = CGLayerCreateWithContext(context, self.frame.size, NULL);
// draw some stuff into CGLayerGetContext(layer)
}
CGContextDrawLayerInRect(context, self.bounds, layer);
The class is also a delegate of its window and has the following function
- (void)windowDidEndLiveResize:(NSNotification *)notification {
if (layer) {
//CGLayerRelease(layer);
layer = nil;
}
[self setNeedsDisplay:YES];
}
Also inside a property setter
- (void)setPitch:(NSArray *)aPitch {
pitch = aPitch;
if (layer) {
//CGLayerRelease(layer);
layer = nil;
}
}
Now if I uncomment the calls to CGLayerRelease then I sporadically get the crash mentioned above. I stress "sporadically" because it does not always happen.
Yes, you need to call CGLayerRelease() in ARC. The above assignments look correct, but I suspect you may have others. You're directly accessing your ivars, which is the #1 cause of these kinds of crashes. Do not access your own ivars except in init and dealloc. Create an accessor for setLayer: that correctly releases the old one and creates a new one and always use that.
It's very possible that your CGLayer code is fine, and that it's something else like a CGPath that you're adding to the CGLayer. Move all your ivars to accessors. Otherwise you will be chasing these kinds of bugs forever.
Related
I have a NSDocument based macOS app with Core Data which by its nature can only ever have one document open at a time. Therefore, when a new document is opened I close the currently open one.
All document related UI is in a separate window controller and everything works well.
But I also have a menu bar item that toggles a separate window which displays some information about the document. The UI is a simple NSTableView bound to an NSArrayController. The array controller's managagedObjectContext property is set when the current document changes. This always leads to a crash with EXC_BAD_INSTRUCTION.
To narrow down the issue I completely removed all bindings and any other manipulation of the array controller. The crash is gone.
I also created a new testArrayController in code to see what happens there and sure enough I could reproduce the crash:
let testArrayController = NSArrayController()
var document: Document? {
didSet {
if document != nil {
testArrayController.managedObjectContext = document?.managedObjectContext
testArrayController.prepareContent() // <---- this causes the crash later on
} else {
testArrayController.managedObjectContext = nil
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
testArrayController.entityName = "MyEntity"
...
}
It seems that calling prepareContent() somehow locks the array controller to a specific managedObjectContext and causes the crash when it is set to nil.
How can I safely "deactivate" an NSArrayController, or change its managedObjectContext?
After lots of experimenting I think I found out that NSArrayController produces a memory leak as soon as you call fetch(_:) or preopareContent(). It seems that it retains its managedObjectContext and never releases it. Even though all other references to the controller have been released I could see the leaked instance in the memory debugger.
I worked around the issue by replacing bindings with a regular NSTableViewDataSource implementation.
I'm trying to implement the animation of a custom property on a CALayer. I've set up the property as dynamic and it mostly works. In my app, I might have a dozen or more instances of this layer, all of which should animate at the same time. However, sometimes when the animation is triggered, a few of the layers don't perform the animation. They simple jump directly to the final value.
In trying to debug the problem, it looks like when the animations start, the layers with the problem get a redraw on their model layer with the final value of the animated property. The rest of the layers only get redraws on their presentation layers with the transient values of the animated property. It's usually the last of the layers that has the problem, but sometimes it's random layers.
It's so sporadic, I'm not really sure where to look next.
Here's the snippet of code that triggers the animation:
#dynamic aspectModeFraction;
- (id<CAAction>)actionForKey:(NSString *)key
{
if ([key isEqualToString:#"aspectModeFraction"])
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animation.fromValue = #([[self presentationLayer] aspectModeFraction]);
animation.duration = 2.0f;
animation.delegate = self;
return animation;
}
return [super actionForKey:key];
}
+ (BOOL)needsDisplayForKey:(NSString *)key {
if ([#"fullRect" isEqualToString:key]) { return YES; }
if ([#"localRect" isEqualToString:key]) { return YES; }
if ([#"screens" isEqualToString:key]) { return YES; }
if ([#"aspectModeFraction" isEqualToString:key]) { return YES; }
return [super needsDisplayForKey:key];
}
It looks like you have most of the parts. There are lots of little parts in custom property animation, and this talk may still be helpful, it it kind of looks like you probably have the major things in place.
I'd first suspect the calling code. I'd make sure you're not suppressing animations for instance, or modifying any UIKit pieces off the main thread.
It's worth noting that the trick of using #dynamic to get your layer properties copied may not be officially supported. (It's one of those things that "seems to work" rather than coming out of any doc I know; and when I've spoken with some Apple engineers about it, they'd never heard of doing it that way.) If you're seeing trouble only on 10.9+, you might try replacing your use of #dynamic with an standard property that you copy over in initWithLayer:.
Thanks for the suggestions. I eventually tracked down the problem. I had optimized the drawing code to only do some calculations when this parameter changed. I was marking the calculations as dirty when the parameter was updated so they would be recalculated during the next draw loop. The dirty flag was set via a custom setter on the parameter. When I switched to #dynamic (which seems to be required for CAAnimations?) the custom setter had to go away, so I was updating the dirty flag by having the object do KVO on itself. It turns out that observeValueForKeyPath:... wasn't being called during the animation. It got one update for the final value, but no intermediates.
Anyway, dropping the optimization and doing the full calculations each time resolved the issue. KVO on the animated value not working as expected seemed to be the culprit.
OK, so Apple brought ARC to us, which is great. After refactoring my Application to ARC almost everything works fine and it is a lot easier now to develop and maintain.
There is just one problem I still can't figure out.
My job management program shows different detail information of proposals, orders and so on in their own windows. So I have a special class where WindowControllers gets allocated and initiated with initWithWindowNibName and then the window is shown with showWindow:
DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:#"ThePorposalWindow"];
[proposalWindowController showWindow:nil];
Before ARC the Instance of the WindowController did the release like shown in the documentation:
- (void)windowWillClose:(NSNotification *)notification
{
[self autorelease];
}
But now with ARC this is not possible anymore and what makes it even worse, in my special class where the WindowController is allocated and initiated, the same windowController is released by ARC because there is no pointer to the windowController.
My idea was to copy the windowController into an mutuable array:
[proposalWindowArray addObject:proposalWindowController];
[[proposalWindowArray lastObject] showWindow:nil];
And in the windowControllers delegate method windowWillClose I post a notification to my special class:
- (void)windowWillClose:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"ProposalWindowWillClose" object:[[self window] windowController] userInfo:nil];
}
In my special class I listen to the notification and remove the object from the array:
- (void) proposalWindowWasClosed: (NSNotification *) notification
{
[proposalWindowArray removeObjectIdenticalTo:[notification object]];
}
It works, but I still do not believe that this is the correct way.
Does anybody has the same problem or a tip to make it better?
I'd probably use a delegate approach rather than notifications. Generally it is better to have an external object that keeps track of the open windows. Self-retaining objects, like your old system, break the basic points of object ownership and make it hard to find things (such as "give me a list of open windows"). Non-Singletons that are just "floating" out there often come back to bite you in your architecture (I've had to fix this often enough).
That said, sometimes self-ownership is at least convenient, and at worst not-the-end-of-the-world. So self-own. The only difference is that you need to do it explicitly rather than matching a leak and an over-release (which is what your old code was doing).
Create a private strong property. Assign self to it. That will create a retain loop that will keep you around until you set the property to nil.
I think your alternative approach should be correct, but I don't think you need the second notification. You should be able to do:
- (void)windowWillClose:(NSNotification *)notification
{
[proposalWindowArray removeObjectIdenticalTo:self];
}
Assuming the "proposalWindowArray" is a static NSMutableArray.
Without hacks, there is no elegant way to keep an object retained other than having a strong reference to it in some other object. For example, you could keep a static NSMutableArray/NSMutableSet, add your controller there, and remove it in windowsWillClose:. This will be shorter than posting a notification. To make this reusable, create a WindowControllerRegistry singleton with an array, where you add controllers like this one, and which will automatically listen to NSWindowWillCloseNotification and remove them from its array thus releasing ownership.
As a quick workaround, you could perform retain/autorelease calls from non-ARC file:
my_retain(self);
my_autorelease(self);
// ArcDisabled.mm
void my_retain(id obj) { [obj retain]; }
void my_autorelease(id obj) { [obj autorelease]; }
I had this same issue when I switched to ARC. Your solution works, but you're making it too complicated. You can essentially do what you were doing before by having the window release itself when it closes, but in an ARC compatible manner.
The solution is to simply create a property of your class within the class itself. For your example, in DetailWindowController, you would add the following property:
#property (strong) DetailWindowController *theWindowController;
Then when you create the window with your code above, add one line like so:
DetailWindowController *proposalWindowController = [[DetailWindowController alloc] initWithWindowNibName:#"ThePorposalWindow"];
[preferenceController setTheWindowController:proposalWindowController];
[proposalWindowController showWindow:nil];
Then finally, to have ARC release the window when it is closed like you did with the autorelease pre-ARC, in the DetailWindowController class, simply do:
- (void)windowWillClose:(NSNotification *)notification
{
// Let ARC tear this down and clean it up
[self setTheWindowController:nil];
}
I created new Document-Based-App.
I implemented dataOfType in subclass of NSDocument
- (NSData*) dataOfType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
return [NSKeyedArchiver archivedDataWithRootObject:bcmwc.bindingsController.arrangedObjects];
}
in xib http://i.minus.com/iH2Rj9v5oOhTn.png
When I click "Save" from menu nothing's gonna happen, any errors in console.
I set a breakpoint in dataOfType, and when I clicked "Save", application didn't stop.
Any suggestions?
EDIT
I think it may be connected with fact I use custom NSWindowController, and custom xib.
I made a test when I load custom xib, everything is fine dataOfType method is invoked etc..
Should I connect in some way my custom xib (window) with subclass of NSDocument?
It looks like your Save menu item is connected properly, so let's concentrate on the code (+1 for having posted it in the first place).
You do nothing in your code to ensure -archivedDataWithRootObject: returns valid data or set an error if it doesn't. My best guess (because you haven't provided nearly enough detail to do anything but guess) is that you're returning nil because your call to -archivedDataWithRootObject: is doing the same. Check to see if you get valid data and set the outError if not.
Why would you get nil? Perhaps one or more of the objects in the object graph created by archiving your array controller's -arrangedObjects array isn't <NSCoding> compliant. This is likely the case if your array controller holds objects of a custom class you created rather than a standard Property List container.
Read the Archives and Serializations Programming Guide (in particular, the Encoding and Decoding Objects section) to learn how to make your custom class <NSCoding> compliant so that it knows how to serialize itself (write itself into a byte stream suitable for NSKeyedArchiver, etc. and create an instance of itself from such a byte stream).
Also, you really need to learn to use the debugger. You're groping around in a dark cave with lots of pitfalls and no flashlight without it. Try setting a breakpoint in methods you expect to be called. If they're not called, you can check outlets/actions, etc. If they are, you can step through each line and make sure everything is executing as you expected. If you write a little more verbose code, you can more easily inspect the results when paused in the debugger. Two lines in your case will help you more than one:
- (NSData*) dataOfType:(NSString *)typeName error:(NSError *__autoreleasing *)outError
{
NSData * myData = [NSKeyedArchiver archivedDataWithRootObject:bcmwc.bindingsController.arrangedObjects];
// You should be handling nil (setting a descriptive error) here if (!myData)...
return data; // breakpoint here; you should now see myData is likely nil
}
I am trying to update another windows when the one becomes visible. So I found the NSWindowDidExposeNotification and tried to work with it, so I wrote in my awakeFromNib:
// MyClass.m
- (void)awakeFromNib {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(mentionsWindowDidExpose:)
name:NSWindowDidExposeNotification
object:nil];
}
and implemented the method
// MyClass.h
- (void)mentionsWindowDidExpose:(id)sender;
// MyClass.m
- (void)mentionsWindowDidExpose:(id)sender {
NSLog(#"test");
}
But it never gets called which is odd. What do I do wrong here?
Generally speaking, you would set up your controller as the window's delegate in order to receive these notifications, like so:
// MyClass.m
- (void)awakeFromNib {
// note: this step can also be done in IB by dragging a connection
// from the window's "delegate" property to your `MyClass` object
[window setDelegate:self];
}
- (void)windowDidExpose:(NSNotification *)notification {
NSLog(#"test");
}
Although, after reading here and here, windowDidExpose may not be your best bet. I would recommend trying the windowDidBecomeKey delegate method instead. That one is posted whenever your window gains "focus" (starts responding to user input) which may be the right time to show your second window.
Update: (in response to comments)
Apple's documentation (quoted below) indicates that NSWindowDidExposeNotification is only valid for nonretained windows, which, according to the posts that I linked above, are quite uncommon.
NSWindowDidExposeNotification
Posted whenever a portion of a nonretained NSWindow object is exposed, whether by being ordered in front of other windows or by other windows being removed from in front of it.
The notification object is the NSWindow object that has been exposed. The userInfo dictionary contains ... the rectangle that has been exposed.
On a higher level, NSNotification objects are simply packages of data that get passed around between Cocoa classes and NSNotificationCenter objects. NSNotificationCenter objects are controllers that manage these packages of data and send them out to observers as required. There is usually no need to trap notifications directly. You can simply use KVC/KVO or pre-defined delegates in your classes and Cocoa handles all of the dirty details behind the scenes.
See Notification Programming Topics and Key Value Coding Programming Guide if you want to know more.