Functionality put in a convenience init - unusable in sub-classes? - cocoa

Isn't functionality put in a convenience init - unusable in sub-classes?
If so, why are the Cocoa's interfaces for Swift defining so many initializers as convenience.
For example - I have a sub-class of NSWindowController and I would like to create a designated init, which will not get any parameters and should directly know what NIB file to instantiate with.
But I don't have any access to super.init's/methods to get the already implemented behaviour and build up on it. Here is the definition of the inits of NSWindowController:
class NSWindowController : NSResponder, NSCoding, NSSeguePerforming, NSObjectProtocol {
init(window: NSWindow?)
init?(coder: NSCoder)
convenience init(windowNibName: String)
convenience init(windowNibName: String, owner: AnyObject)
convenience init(windowNibPath: String, owner: AnyObject)
// ...
}
Instead I am forced to reimplement the NIB loading, thus duplicating and potentially getting it wrong.
Edit:
Here is a small passage from a blogpost by Mike Ash, mentioning NSWindowController subclasses and the reasoning behind what I do in my case is exactly the same:
NSWindowController provides a initWithWindowNibName: method. However, your subclass is built to work with only a single nib, so it's pointless to make clients specify that nib name. Instead, we'll provide a plain init method that does the right thing internally. Simply override it to call super and provide the nib name:
- (id)init
{
return [super initWithWindowNibName: #"MAImportantThingWindow"];
}
So it's possible in ObjectiveC, but how can this be done in Swift?

Convenience initializers are inherited in subclasses. They can be overriden, too.
In order to call init(windowNibName: String), you need to declare a convenience initializer to call it from, and you should call it on self, rather than super:
class MAImportantThingWindowController : NSWindowController {
override convenience init() {
self.init(windowNibName: "MAImportantThingWindow")
}
}

Related

NSView with a KVC property in Swift

I have a custom NSView class defined as:
class MyView: NSView
{
var someText: NSString
override func didChangeValueForKey(key: String)
{
println( key )
super.didChangeValueForKey( key )
}
// other stuff
}
What I want to be able to do is from outside of this class change the value of someText and have didChangeValueForKey notice that someText has changed so I can, for example, set needsDisplay to true for the view and do some other work.
How an I do this?
Are you sure you need KVC for this? KVC works fine in Swift, but there’s an easier way:
var SomeText: NSString {
didSet {
// do some work every time SomeText is set
}
}
There is no KVC mechanism for this because this isn't what KVC is for.
In Objective-C, you would implement the setter explicitly (or override if the property is originally from a superclass) and do your work there.
In Swift, the proper approach is the didSet mechanism.
didChangeValueForKey() is not part of KVC, it's part of KVO (Key-Value Observing). It is not intended to be overridden. It's intended to be called when one is implementing manual change notification (as a pair with willChangeValueForKey()).
More importantly, though, there's no reason to believe that it will be called at all for a property which is not being observed by anything. KVO swizzles the class in order to hook into the setters and other mutating accessors for those properties which are actually being observed. When such a property is changed (and supports automatic change notification), KVO calls willChangeValueForKey() and didChangeValueForKey() automatically. But for non-observed properties, those methods are not called.
Finally, in some cases, such as the indexed collection mutation accessors, KVO will use different change notification methods, such as willChange(_:valuesAtIndexes:forKey:) and didChange(_:valuesAtIndexes:forKey:).
If you really don't want to use didSet for some reason, you would use KVO to observe self for changes in the someText property and handle changes in observeValueForKeyPath(_:ofObject:change:context:). But this is a bad, clumsy, error-prone, inefficient way of doing a simple thing.
KVO and didSet are not mutually exclusive:
import Foundation
class C: NSObject {
dynamic var someText: String = "" {
didSet {
print("changed to \(someText)")
}
}
}
let c = C()
c.someText = "hi" // prints "changed to hi"
class Observer: NSObject {
init(_ c: C) {
super.init()
c.addObserver(self, forKeyPath: "someText", options: [], context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print("observed change to \(object!.valueForKeyPath(keyPath!))")
}
}
let o = Observer(c)
c.someText = "test" // prints "changed to test" and "observed change to test"
I would add to Jaanus's answer that to make the property KVC compliant, you should declare it as dynamic var someText: NSString.
But if you don't need all the bells and whistles oh KVC, didSet is the way to go.
Update
As for didChangeValueForKey: – it is intended for the opposite, for you to notify value for key has changed (if it is not due to one of the cases covered by Foundation). You should use addObserver(_:forKeyPath:options:context:) and override observeValueForKeyPath(_:ofObject:change:context:) to be notified of changes.
Alternatively you can use one of many 3rd party solutions such as ReactiveCococa or Facebook's KVOController

Adopting NSTextFinderBarContainer protocol in Swift forces variable initialization despite header comment

I have an NSView subclass that implements the NSTextFinderBarContainer protocol. Part of the NSTextFinderBarContainer protocol is implementing
var findBarView: NSView { get set }
However the comment above this property in the original Objective-C header is:
This property is used by NSTextFinder to assign a find bar to a
container. The container may freely modify the view's width, but
should not modify its height. This property is managed by
NSTextFinder. You should not set this property.
Because Swift requires all instance variables to be initialized, how do I handle this situation? It appears Swift requires me to go against what Apple has wrote in the header: you should not set this property as it will be set/managed by the NSTextFinder itself.
If I don't override the NSView initializers I get:
Class 'ExampleContainerView' has no initializers
As expected since findBarView does not have an initial value.
The relevant parts of my Swift code are:
class ExampleContainerView: NSView, NSTextFinderBarContainer {
var findBarView : NSView
...
}
If I override the designated initializer to initialize findBarView as follows (ignoring Apple's comment in the header):
required init?(coder: NSCoder) {
findBarView = NSView(frame: NSRect())
super.init(coder: coder)
}
The app crashes after the NSTextFinder is sent the setFindBarContainer: message
-[NSView _setTextFinder:]: unrecognized selector sent to instance 0x6000001278a0
The object at 0x6000001278a0 is the NSView instance set in the overridden initializer above.
This appears fixed as of Xcode 7.0 beta 6. NSTextFinderBarContainer now declares findBarView as an optional NSView:
public var findBarView: NSView? { get set }
In addition, contentView() also changed to return an optional NSView:
optional public func contentView() -> NSView?
Making the property optional means there is no longer the contradiction of having the API comments say not to set findBarView, while having Swift require that all non-optional properties are initialized in in your initializers.

NSWindowController in Swift. Subclassing and initializing with Nib

In a test Swift project, I am subclassing NSWindowController. My NSWindowController subclass is designed to work with a particular Nib file. It is desirable, then, that when my window controller is initialized, the nib file is automatically loaded by the window controller instance. In Objective-C, this was achieved by doing:
#implementation MyWindowController
- (id)init {
self = [super initWithWindowNibName:"MyWindowNib"]
if (self) {
// whatever
}
return self
}
#end
Now, in Swift this is not possible: init() cannot call super.init(windowNibName:), because the later is declared not as a designated initializer, but as a convenience one by NSWindowController.
How can this be done in Swift? I don't see a strightforward way of doing it.
P.S.: I have seen other questions regarding this topic, but, as long as I've been able to understand, the solutions all point to initialize the Window Controller by calling init(windowNibName:). Please note that this is not the desired beheaviour. The Window Controller should be initialized with init(), and it should be the Window Controller itself who "picks up" its Nib file and loads it.
If you use the init() just to call super.init(windowNibName:), you could instead just override the windowNibName variable.
override var windowNibName: String {
get {
return "MyWindowNib"
}
}
Then there should be no need to mess with the initializers.
You can create your own convenience initializer instead:
override convenience init() {
self.init(windowNibName: "MyWindowNib")
}
You should instead opt in to replacing all designated initializers in your subclass, simply delegating to super where appropriate. Confer https://stackoverflow.com/a/24220904/1460929

How to initialize a NSWindowController in Swift?

I want to initialize a window controller object from a nib file, quite easy right? But I simply can't get it to work.
According to my previous experience in ObjC, I've written down the following code:
init() {
super.init(windowNibName: "SplitWindowController")
}
And in the app delegate file, I simply init and displays the window:
var myWindowController: MyWindowController = MyWindowController()
myWindowController.showWindow(self)
myWindowController.window.makeKeyAndOrderFront(nil)
But the compiler gives me this error: Must call a designated initializer of the superclass 'NSWindowController'. And according to the Swift version of NSWindowController definition, there are only 3 designated initializers, namely init(), init(window), init(coder). I don't know what to do next. Shall I build a NSCoder from a nib file, which I don't know how to do?
You were almost there. You can indeed override init() as a convenience initialiser in a manner that is equivalent to the Obj-C code you got used to:
import Cocoa
class MyWindowController: NSWindowController {
override convenience init() {
self.init(windowNibName: "<xib name>")
}
}
Note that you are calling init(windowNibName:) on self, because init() being a convenience initialiser, you still inherit all the initialisers from the superclass. From documentation:
Rule 1: A designated initializer must call a designated initializer
from its immediate superclass.
Rule 2: A convenience initializer must call another initializer from
the same class.
Rule 3: A convenience initializer must ultimately call a designated
initializer.
Also, as #weichsel mentioned above, make sure you set the class of the File's Owner to your subclass of NSWindowController (in the example above, that would be MyWindowController) and then connect its window outlet with the window itself.
That being said, I'm not sure why is compiler asking for the override keyword to be added. Though NSWindowController is a subclass of NSResponder, which defines an init(), the following code compiles without issue even though it implements an equivalent inheritance hierarchy:
class A {
init() { }
}
class B: A {
init(Int) {
super.init()
}
convenience init(String) {
self.init(5)
}
}
class C: B {
convenience init() {
self.init("5")
}
}
NSWindowController has 2 designated initializers:
init(window: NSWindow!)
init(coder: NSCoder!)
When creating a subclass, you should invoke the designated initializer of its superclass. Recent versions of Xcode enforce this. Either via built-in language mechanism (Swift) or via NS_DESIGNATED_INITIALIZER macro (Objective-C).
Swift additionally requires that you call the superclasses designated initializer when you override a convenience initializer.
From the "Initialization: Designated Initializers and Convenience Initializers" section of Swift Programming Guide:
If the initializer you are overriding is a convenience initializer,
your override must call another designated initializer from its own
subclass, as per the rules described above in Initializer Chaining.
In your case, you should probably override init(window: NSWindow!) and call super's counterpart from there.

Xcode override all methods

I want override all methods of a subclass automatically on xcode, for example I have a class extended of UiViewControler, how I override all methods of UiViewController on xcode to be more or less well:
- (id) init
{
return [super init];
}
My intention with this is to log all methods to see when they are called, then my methods will be more or less well
- (id) init
{
[self log];
return [super init];
}
where log is as follow method:
-(void) log
{
NSLog(#"%#",[(NSString *) (NSArray *) [NSThread callStackSymbols][1] componentsSeparatedByString:#"-["][1]);
}
thanks a lot!
In this case you don't have to do anything. If you don't provide an implementation, then the superclass's implementation will be used.
Edited after the question was edited
If you put the log statement in the superclass's implementation then it doesn't matter what you do with your own initialiser.
Why?
One of the many conventions in Cocoa is that each class has a designated initialiser. All the other designated initialisers then call this initialiser. And when you subclass the class, then you create a new designated initialiser for the new class, and as part of the initialisation - this calls the superclass's designated initialiser.
Which is why you see NSObject subclass initialisers calling [super init], because NSObject's designated initialiser is init.
So, just call your logging method in the designated initialiser of your class, and as long as you follow the above convention, this initialiser will always be called by a subclass, and so your logging method will always be called.

Resources