Converting self-releasing objects to ARC - macos

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];
}

Related

How to use NSVisualEffectView backwards-compatible with OSX < 10.10?

The upcoming OSX 10.10 ("Yosemite") offers a new type of view, NSVisualEffectView, which supports through-the-window or within-the-window translucency. I'm mostly interested in through-the-window translucency, so I'm going to focus on that in this question, but it applies to within-the-window translucency as well.
Using through-the-window translucency in 10.10 is trivial. You just place an NSVisualEffectView somewhere in your view hierarchy and set it's blendingMode to NSVisualEffectBlendingModeBehindWindow. That's all it takes.
Under 10.10 you can define NSVisualEffectViews in IB, set their blending mode property, and you're off and running.
However, if you want to be backwards-compatible with earlier OSX versions, you can't do that. If you try to include an NSVisualEffectView in your XIB, you'll crash as soon as you try to load the XIB.
I want a "set it and forget it" solution that will offer translucency when run under 10.10 and simply degrade to an opaque view when run on earlier OS versions.
What I've done so far is to make the view in question a normal NSView in the XIB, and then add code (called by awakeFromNib) that checks for [NSVisualEffectView class] != nil, and when it's the class is defined, I create an instance of the NSVisualEffectView, move all my current view's subviews to the new view, and install it in place. This works, but it's custom code that I have to write every time I want a translucent view.
I'm thinking this might be possible using an NSProxy object. Here's what I'm thinking:
Define a custom subclass of NSView (let's call it MyTranslucentView). In all the init methods (initWithFrame and initWithCoder) I would throw away the newly created object and instead create a subclass of NSProxy that has a private instance variable (myActualView). At init time it would decide to create the myActualView object as an NSVisualEffectView if OS>=10.10, and a normal NSView under OS<10.10.
The proxy would forward ALL messages to it's myActualView.
This would be a fair amount of fussy, low-level code, but I think it should work.
Has anybody done something like this? If so, can you point me in the right direction or give me any pointers?
Apple is MUCH more open with the Beta agreement with Yosemite a than with previous Betas. I don't think I'm violating my Beta NDA by talking about this in general terms, but actual code using NSVisualEffectView would probably need to be shared under NDA...
There is a really simple, but somewhat hacky solution: Just dynamically create a class named NSVisualEffectView when your app starts. Then you can load nibs containing the class, with graceful fallback on OS X 10.9 and earlier.
Here's an extract of my app delegate to illustrate the idea:
AppDelegate.m
#import "AppDelegate.h"
#import <objc/runtime.h>
#implementation PGEApplicationDelegate
-(void)applicationWillFinishLaunching:(NSNotification *)notification {
if (![NSVisualEffectView class]) {
Class NSVisualEffectViewClass = objc_allocateClassPair([NSView class], "NSVisualEffectView", 0);
objc_registerClassPair(NSVisualEffectViewClass);
}
}
#end
You have to compile this against the OS X 10.10 SDK.
How does it work?
When your app runs on 10.9 and earlier, [NSVisualEffectView class] will be NULL. In that case, the following two lines create a subclass of NSView with no methods and no ivars, with the name NSVisualEffectView.
So when AppKit now unarchives a NSVisualEffectView from a nib file, it will use your newly created class. That subclass will behave identically to an NSView.
But why doesn't everything go up in flames?
When the view is unarchived from the nib file, it uses NSKeyedArchiver. The nice thing about it is that it simply ignores additional keys that correspond to properties / ivars of NSVisualEffectView.
Anything else I need to be careful about?
Before you access any properties of NSVisualEffectView in code (eg material), make sure that the class responds to the selector ([view respondsToSelector:#selector(setMaterial:)])
[[NSVisualEffectView alloc] initWithFrame:] still wont work because the class name is resolved at compile time. Either use [[NSClassFromString(#"NSVisualEffectView") alloc] initWithFrame:], or just allocate an NSView if [NSVisualEffectView class] is NULL.
I just use this category on my top-level view.
If NSVisualEffects view is available, then it inserts a vibrancy view at the back and everything just works.
The only thing to watch out for is that you have an extra subview, so if you're changing views around later, you'll have to take that into account.
#implementation NSView (HS)
-(instancetype)insertVibrancyViewBlendingMode:(NSVisualEffectBlendingMode)mode
{
Class vibrantClass=NSClassFromString(#"NSVisualEffectView");
if (vibrantClass)
{
NSVisualEffectView *vibrant=[[vibrantClass alloc] initWithFrame:self.bounds];
[vibrant setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
[vibrant setBlendingMode:mode];
[self addSubview:vibrant positioned:NSWindowBelow relativeTo:nil];
return vibrant;
}
return nil;
}
#end
I wound up with a variation of #Confused Vorlon's, but moving the child views to the visual effect view, like so:
#implementation NSView (Vibrancy)
- (instancetype) insertVibrancyView
{
Class vibrantClass = NSClassFromString( #"NSVisualEffectView" );
if( vibrantClass ) {
NSVisualEffectView* vibrant = [[vibrantClass alloc] initWithFrame:self.bounds];
[vibrant setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
NSArray* mySubviews = [self.subviews copy];
for( NSView* aView in mySubviews ) {
[aView removeFromSuperview];
[vibrant addSubview:aView];
}
[self addSubview:vibrant];
return vibrant;
}
return nil;
}
#end

Manual binding in Cocoa

I have an ImageView which shows a lock, informing if an opened file is locked or not. I have 2 images for locked and unlocked cases. I want synchronize the displayed image with boolean value of my object representing an opened file.
To do this I want my ViewController to change the image in my ImageView depending on lock state of object. So both object and ViewController have a property "isLocked".
How can I synchronize them? It is easy in IB but I don't know how to do it programmatically. I tried in initialize method of my ViewController to use:
[ViewController bind:#"value" toObject:[ArrayController selection] withKeyPath:#"isLocked" options:nil];
But it doesn't work. In documentation it is said that I have to expose my binding before using it.
I try to put the following code in initializer method of my object:
[self exposeBinding:#"isLocked"];
But Xcode doesn't recognize this method.
Does somebody have experience with this kind of bindings establishing?
As #nick says, you want Key-Value-Observing.
[arrayController addObserver:self
forKeyPath:#"selection.isLocked"
options:NSKeyValueObservingOptionNew
context:#"this_context"]
Then when isLocked changes the -observeValueForKeyPath:ofObject:change:context: method that you have added to your viewController will be called (as long as you only manipulate isLocked in a KVC compliant way).
The options parameter lets you optionally tweak exactly what conditions will trigger the notification and what data is sent along with the notification. The context parameter is there to help you distinguish between notifications that you registered to receive and notifications your superclass registered to receive. It is optional.
Bindings seem like they might be useful to keep two values in sync. However, this is not what they do at all.
Yes, lots of things seem to give the impression that this is what they do, and there isn't much saying that this isn't what they do, also lots of people believe that this is what they do - but no, you cannot use them for this.
Only a handful of classes support bindings (they are listed here) and then, and this is the important bit, those classes only support binding their named bindings, and these bindings are not instance variables. eg NSTextField has a 'fontFamilyName' binding yet NSTextField does not have a 'fontFamilyName' property or instance variable, even a derived one. NSTextField does have a 'isBordered' property but not a binding - so you cannot bind 'isBordered'.
It does not mean anything to 'bind' an arbitrary property of an arbitrary Class.
Yes, you can bind two arbitrary values, the following code works just fine:
#import <Cocoa/Cocoa.h>
#interface SomeObject : NSObject
#property (retain,nonatomic) id someValue;
#end
#implementation SomeObject
#end
int main()
{
SomeObject *source=[SomeObject new];
SomeObject *target=[SomeObject new];
[target bind:#"someValue" toObject:source withKeyPath:#"someValue" options:0];
[source bind:#"someValue" toObject:target withKeyPath:#"someValue" options:0];
[source setSomeValue:#(42)];
NSLog(#"target: %#",[target someValue]);
[target setSomeValue:#(22)];
NSLog(#"source: %#",[source someValue]);
return 0;
}
As far as I can tell, the problem is the bit [ArrayController selection]. The first problem is that ArrayController is (or should be) a class, and getting the class's selection is probably pointless. The other problem is that even if this were an instance, you would be binding to the selection at the time of the call, which is almost certainly not what you want. You want to track the current selection as it changes.
So what you want is probably something like the following:
[myViewController bind:#"value" toObject:myArrayController withKeyPath:#"selection.isLocked" options:nil];

Placement of [super methodName];

I've seen this done both ways, with the [super methodName]; as the first line in a method and as the last line. Is there a best way to do this?
For example:
- (void)viewDidLoad {
[super viewDidLoad];
self.intX = 1;
}
- (void)viewDidLoad {
self.intX = 1;
[super viewDidLoad];
}
- (void)dealloc {
[myControl release];
[super dealloc];
}
In general, when dealing with views you want to take into account the standard behaviour in relation to the changes that your subclass will take.
This is best illustrated with
- (void)drawRect:(NSRect)dirtyRect
Effectively you are drawing your view into the rect but the view itself is doing all of the highlighting and sub view management in there as well.
So, do you need to do something that will occur before or after the super class does it's normal behaviour?
In these cases it is best to test by moving your code before and after the super call. It may also yield some behaviour understanding by not even making the super call to see how it affects what is done.
viewDidLoad is more of an initialiser and typically is more relevant to your subclassed ivars than the actual class itself. If however your subclassed viewDidLoad is affecting the ivars or state of the subclassed view; you likely want to wait until after super so that your changes are't blown away.
In dealloc we call [super methodName]; at last, because before releasing super class you need to clean up child class objects, otherwise they will become orphans.
For example:
Just think about this example. will it make sense to you ?
You will use your dad's money for to get a good education then you will start earning (In your question, get superclass's benifits before you create your own subclass). At your bad time, first you will loose your money and slowly you may drain your dad's money too (In dealloc method, release subclass's object then super class).

How to use NSWindowDidExposeNotification

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.

Where do you put cleanup code for NSDocument sub-classes?

I have a document-based application and I have sub-classed NSDocument and provided the required methods, but my document needs some extensive clean-up (needs to run external tasks etc). Where is the best place to put this? I have tried a few different methods such as:
close
close:
canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo
dealloc
If I put it in dealloc, sometimes it gets called and other times it does not (pressing Command+Q seems to bypass my document's deallocation), but it is mandatory that this code gets called without failure (unless program unexpectedly terminates).
Have each document add itself as an observer in the local notification center for NSApplicationWillTerminateNotification. In its notification method, call its clean-up method (which you should also call from dealloc or close).
The correct answer here didn't fit my use case, but the question does. Hence the extra answer.
My use case: closing a document (which may be one of several that are open) but not closing the application.
In this case (at time of writing and unless I'm just looking in the wrong place) the documentation is not as helpful as it could be.
I added a canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo: override in my NSDocument subclass and called super within it. The documentation doesn't say whether you must call super, but a bit of logging shows that the system is providing a selector and a context. This method is called just before the document is closed.
- (void) canCloseDocumentWithDelegate:(id)delegate shouldCloseSelector:(SEL)shouldCloseSelector contextInfo:(void *)contextInfo;
{
if ([self pdfController])
{
[[[self pdfController] window] close];
[self setPdfController: nil];
}
[super canCloseDocumentWithDelegate:delegate shouldCloseSelector: shouldCloseSelector contextInfo: contextInfo];
}
There is some useful discussion of this method on CocoaBuilder. If there's downsides to this approach or better ways of doing this, please comment.

Resources