Deleting CAMetalLayer for NSView - calayer

I have a custom NSView class which is layer backed. I make a CAMetalLayer for this NSView which is created in makeBackingLayer.
In makeBackingLayer I create a layer CAMetalLayer *backingLayer = [CAMetalLayer layer]; and set properties as required by me.
My question is, do I need to dealloc this layer explicitly while destructing NSView?
I do not create this layer in an overridden function, is it my responsibility to delete this or the NSView will take care of it?
I do not see any documentation around this. Moreover, all the samples that I see do not mention about deleting layers anywhere.
Thanks

You likely do not need to manually free your layer. Assuming your program has ARC (Automatic Reference Counting) enabled, your NSView should automatically free whichever CALayer is set to its .layer property upon its destruction.
If you're not sure if ARC is enabled, you can go to Build Settings in your Xcode project and search for Automatic Reference Counting. It's been on by default for new Xcode projects for several years now.
Note: Your NSView will only be able to free your layer if it is the only object holding a reference to it. If other objects in your program are holding references to your CAMetalLayer, your layer would not be freed until they remove their references.

Related

Framework uses drawRect:; I need to use it on a layer-backed NSView. How to update?

According to the documentation for NSView's drawRect:
If your app manages content using its layer object instead, use the updateLayer method to update your layer instead of overriding this method.
I have an NSView with subviews that are provided by the framework, and they all draw using drawRect:. This framework-provided view is a subview of an NSView for which I require a layer. Because my framework-provided view is a descendant of a layer-backed view, drawRect: isn't usually called, especially in cases where the window is made active or inactive (the view needs to update to reflect its (in)active state).
Of course if I make my containing view not layer backed, updates occur when the window is made active or inactive.
Without modifying the framework into a custom fork, what's the best avenue for making sure drawRect: occurs when needed in my framework-provided view?
Thanks.
Edit 25-Aug-2018:
It looks like the trick is to set one of the views in the hierarchy to, e.g., [view setCanDrawSubviewsIntoLayer:YES, which according to the documentation uses all of the subviews’ drawRect: to add their drawing to its own layer. However this seems to work only through 10.13, and is broken in the 10.14 beta. I'll continue to look for a potential API change, unless this is a 10.14 beta bug.
Since the issue is still unresolved, it's not really answered yet.
Layer-backed views which don't override -wantsUpdateLayer to return true still draw themselves using -drawRect:. The bit of documentation you quoted is using "should" to mean "should, for best performance,". It's not required, it's just recommended.
Views don't generally redraw themselves just because the containing window has changed key or main status. You would have to mark them as needing display. Or the framework should be doing that.
I suspect the reason that it works when your view is not layer-backed is that you are marking your view as needing update. Since non-layer-backed views draw into the window's backing store using the painter model (back to front), if your view redraws itself then any subviews will have to redraw themselves on top of your view's drawing.
If the framework's views need to redraw when the window's key/main status changes, then they should be observing the relevant notifications and setting themselves as needing display. If they're not doing that, it's a framework bug. You can work around it by marking them as needing display yourself.

why delegates should be unsafe_unretained and not weak?

I added ARC to an app I'm working on. Unfortunately, it crashes. I found that the automatic script which updates all apps to ARC gave __unsafe_unretained qualifier to all id< protocolName> type.
Why isn't it a weak type? I have deployed the app and all its sub-projects to iOS 5, and therefore I do have weak qualifiers.
My main problem is if I declare those delegates as strong, I'll have a retain-cycle. If I do not, the next time I call them they will be zombies. I checked and before my app crash, the delegate is NSZombie.
What is the cause of this crash and how can it be prevented?
The qualifiers __unsafe_unretained and week have quite a few things in common. They both won't increase the retain count for example. If for example a view controller holds an __unsafe_unretained IBOutlet to a UIView and you remove that very UIView from the view hierarchy, then you (given that you don't retain the view anywhere else) will decrease the retain count and most likely dealloc the UIView. The pointer however will still point to that location and is left dangling. Not nice but also not problematic if you know what happened. Weak properties help you avoiding dangling pointers by nullifying the property when the object gets to a retain count of 0.
Now, if your app crashes or the properties show up as zombies, then they are being released - by whichever class though.
One statement that is not entirely correct is that if you retain the property instead, you'll create a retain cycle. There is the possibility of creating retain cycles though but it really depends on your implementation, not just the property declaration. When you retain an object, you take ownership and until you're done with that object, prevent it from being deallocated by increasing its retain count. If your delegate gets released already while you hold a weak pointer, you won't prevent it from being released. I am assuming you deal with modal view controllers here - UIPopoverController to be precise (just a guess).
You should use instruments and look at the lifecycle of your object and see who retains/releases it. It could be helpful to know. Otherwise, you could paste some code and maybe there will be a nice person here to help you find the issue.
cheers
Ronny
Took some time but i solved it:
I deployed the .xcodeproj projects to iOS 5, but the targets were left in iOS 4.3 deployment. When i fixed it (it's in the 'build settings' for each target) - i could change all '__unsafe_unretained' to '__weak', and all 'unsafe_unretained' to 'weak'.
To avoid retain cycle those delegates should be weak, and they won't be zombies anymore (because they are weak and not unsafe_unretained), and the app won't crash anymore.
If i was still using iOS4.3-, and there isn't unsafe_unretained qualifer, i should only assign nil to those delegates after i don't need them anymore.

Why might an NSTableView redraw every cell on scroll?

I have an NSTableView with 5 columns, each containing a stock NSTableCellView in the nib. (The stock cells have a text box and an optional image.) When populated, the table has around 50 rows. Everything displays fine, but scrolling performance is pretty bad. It looks like this is happening because every cell gets a drawRect: message for its full rect whenever the table scrolls. However, neither reloadData nor reloadDataForRowIndexes:ColumnIndexes: is getting called, so it's not that. It's not the contents of the cells, either: I tried commenting out all my code to just leave the default cell image and text for each cell, and performance is the same. While scrolling, none of the cells get updated. (I put a breakpoint in tableView:viewForTableColumn:row: to make sure.)
My implementation has the following delegate methods:
tableView:viewForTableColumn:row: in the delegate; this creates and populates new cells via makeViewWithIdentifier:owner:
numberOfRowsInTableView: in the data source; this returns a constant number
tableView:sortDescriptorsDidChange: in the data source
That's it! Not very complicated, and yet.
I feel like I'm missing something completely obvious. What could be causing these redraws?
EDIT: Come to think of it, several other applications (uTorrent, Xcode) seem to exhibit the same slow scrolling behavior. You can really see it if you look at CPU usage while scrolling. On the other hand, Activity Monitor has buttery-smooth scrolling that barely spikes the CPU at all. How do I get that in my app?
EDIT 2: I think I found my mistake. According to Apple:
In iOS apps, Core Animation is always enabled and every view is backed
by a layer. In OS X, apps must explicitly enable Core Animation
support by doing the following:
Link against the QuartzCore framework. (iOS apps must link against this framework only if they use Core Animation interfaces explicitly.)
Enable layer support for one or more of your NSView objects by doing one of the following:
In your nib files, use the View Effects inspector to enable layer support for your views. The inspector displays checkboxes for
the selected view and its subviews. It is recommended that you enable
layer support in the content view of your window whenever possible.
For views you create programmatically, call the view’s setWantsLayer: method and pass a value of YES to indicate that the
view should use layers.
Enabling layer support in one of the preceding ways creates a
layer-backed view. With a layer-backed view, the system takes
responsibility for creating the underlying layer object and for
keeping that layer updated. In OS X, it is also possible to create a
layer-hosting view, whereby your app actually creates and manages the
underlying layer object. (You cannot create layer-hosting views in
iOS.) For more information on how to create a layer-hosting view, see
“Layer Hosting Lets You Change the Layer Object in OS X.”
I'll add an answer as soon as I fix my performance issues. With a cursory pass, my scrolling is still bumpy, but my CPU usage has dropped from 70% to 10% while scrolling.
For the record... Edit 2 by the OP makes the world of difference.
In iOS apps, Core Animation is always enabled and every view is backed
by a layer. In OS X, apps must explicitly enable Core Animation
support by doing the following:
Link against the QuartzCore framework. (iOS apps must link against
this framework only if they use Core Animation interfaces explicitly.)
Enable layer support for one or more of your NSView objects by doing
one of the following:
In your nib files, use the View Effects inspector to enable layer
support for your views. The inspector displays checkboxes for the
selected view and its subviews. It is recommended that you enable
layer support in the content view of your window whenever possible.
For views you create programmatically, call the view’s setWantsLayer:
method and pass a value of YES to indicate that the view should use
layers. Enabling layer support in one of the preceding ways creates a
layer-backed view. With a layer-backed view, the system takes
responsibility for creating the underlying layer object and for
keeping that layer updated. In OS X, it is also possible to create a
layer-hosting view, whereby your app actually creates and manages the
underlying layer object. (You cannot create layer-hosting views in
iOS.) For more information on how to create a layer-hosting view, see
“Layer Hosting Lets You Change the Layer Object in OS X.”

replaceSubview:with and ARC is releasing old view

I have an issue when using NSView's replaceSubview:with: method to swap out different views. The old view is released when the method is called, the docs state,
This method causes oldView to be released; if you plan to reuse it, be
sure to retain it before sending this message and to release it as
appropriate when adding it as a subview of another NSView.
However, when using automatic reference counting (ARC) retain messages cannot be sent. Do the docs need to be updated, how can I use this method with ARC?
The views I am swapping exist all in the same nib and I do not have different view controllers. What is the preferred way of swapping out views and storing them for later use?
First store the old view for later use in a strong variable and then swap it out should prevent it from being released.

How do I prevent a CALayer from redrawing as its bound change?

i have a CALayer with a custom draw method I've added to my view's base layer. I set needsDisplayOnBoundsChange to NO. However, when I resize the parent view's frame, the layer's drawInContext: is getting called continuously. I'd like the contents to scale while the resize is occurring. Any clues?
Interesting, I have a case where I have a CALayer that correctly scales its contents until I call setNeedsDisplay on it to redraw its contents. One thing that may be different is that in my case the layer is being drawn by its delegate and not by a subclass of CALayer. Another thing that may be different is that this is on iOS and not OSX (I don't know which you are using in this case). It is possible that there could be behavior differences between subclasses and delegate drawn layers and/or iOS and OSX.
Another thing to note is that needsDisplayOnBoundsChange is documented to be NO by default, so one should not need to set it. I am not specifically setting needsDisplayOnBoundsChange on my layer.
You could try using a delegate to do the drawing to see if that makes a difference. Note that a UIView cannot be a delegate to a CALayer. In my case I made a simple delegate object that forwards the draw call to my view.

Resources