Adding Convenience Initializers in Swift Subclass - ios

As a learning exercise I am trying to implement a subclass of SKShapeNode that provides a new convenience initializer that takes a number and constructs a ShapeNode that is a square of number width and height.
According to the Swift Book:
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.”
However, the following class doesn't work:
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init(rectOfSize: CGSizeMake(value, value))
}
}
Instead I get:
Playground execution failed: error: <REPL>:34:9: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:34:14: error: use of 'self' in delegating initializer before self.init is called
self.init(rectOfSize: CGSizeMake(value, value))
^
<REPL>:35:5: error: self.init isn't called on all paths in delegating initializer
}
My understanding is that MyShapeNode should inherit all of SKShapeNode's convenience initializers because I am not implementing any of my own designated initializers, and because my convenience initializer is calling init(rectOfSize), another convenience initializer, this should work. What am I doing wrong?

There are two problems here:
SKShapeNode has only one designated initializer: init(). This means that we cannot get out of our initializer without calling init().
SKShapeNode has a property path declared as CGPath!. This means that we don't want to get out of our initializer without somehow initializing the path.
The combination of those two things is the source of the issue. In a nutshell, SKShapeNode is incorrectly written. It has a property path that must be initialized; therefore it should have a designated initializer that sets the path. But it doesn't (all of its path-setting initializers are convenience initializers). That's the bug. Putting it another way, the source of the problem is that, convenience or not, the shapeNodeWith... methods are not really initializers at all.
You can, nevertheless, do what you want to do — write a convenience initializer without being forced to write any other initializers — by satisfying both requirements in that order, i.e. by writing it like this:
class MyShapeNode : SKShapeNode {
convenience init(squareOfSize value: CGFloat) {
self.init()
self.init(rectOfSize: CGSizeMake(value, value))
}
}
It looks illegal, but it isn't. Once we've called self.init(), we've satisfied the first requirement, and we are now free to refer to self (we no longer get the "use of 'self' in delegating initializer before self.init is called" error) and satisfy the second requirement.

My understanding of Initializer Inheritance is the same as yours, and I think we are both well aligned with what the book states. I don't think it's an interpretation issue or a misunderstanding of the stated rules. That said, I don't think you're doing anything wrong.
I tested the following in a Playground and it works as expected:
class RectShape: NSObject {
var size = CGSize(width: 0, height: 0)
convenience init(rectOfSize size: CGSize) {
self.init()
self.size = size
}
}
class SquareShape: RectShape {
convenience init(squareOfSize size: CGFloat) {
self.init(rectOfSize: CGSize(width: size, height: size))
}
}
RectShape inherits from NSObject and doesn't define any designated initializers. Thus, as per Rule 1, it inherits all of NSObject's designated initializers. The convenience initializer I provided in the implementation correctly delegates to a designated initializer, prior to doing the setup for the intance.
SquareShape inherits from RectShape, doesn't provide a designated initializer and, again, as per Rule 1, inherits all of SquareShape's designated initializers. As per Rule 2, it also inherits the convenience initializer defined in RectShape. Finally, the convenience initializer defined in SquareShape properly delegates across to the inherited convenience initializer, which in turn delegates to the inherited designated initializer.
So, given the fact you're doing nothing wrong and that my example works as expected, I am extrapolating the following hypothesis:
Since SKShapeNode is written in Objective-C, the rule which states that "every convenience initializer must call another initializer from the same class" is not enforced by the language. So, maybe the convenience initializer for SKShapeNode doesn't actually call a designated initializer. Hence, even though the subclass MyShapeNode inherits the convenience initializers as expected, they don't properly delegate to the inherited designated initializer.
But, again, it's only a hypothesis. All I can confirm is that the mechanics works as expected on the two classes I created myself.

Building on Matt's answer, we had to include an additional function, or else the compiler complained about invoking an initializer with no arguments.
Here's what worked to subclass SKShapeNode:
class CircleNode : SKShapeNode {
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init()
self.init(circleOfRadius: width/2)
// Do stuff
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Good news from 2019! I can report that I now have a SKShape subclass that has the following three initializers:
override init() {
super.init()
}
convenience init(width: CGFloat, point: CGPoint) {
self.init(circleOfRadius: width/2)
self.fillColor = .green
self.position = point
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
and that behaves exactly as expected: when you call the convenience initializer, you get green dots in the desired position. (The double calling of init() as described by #matt and #Crashalot, on the other hand, now results in an error).
I'd prefer to have the ability to modify SKShapeNodes in the .sks scene editor, but you can't have everything. YET.

Related

UNNotificationCategory subclass init issue

I want to subclass UNNotificationCategory(UserNotifications), because I want to use enums instead of hard coded strings as category identifiers.
There is one convenience init inside UNNotificationCategory definition
public convenience init(identifier: String, actions: [UNNotificationAction], intentIdentifiers: [String], options: UNNotificationCategoryOptions = [])
I am not able to write an initializer for my subclass.
I understand I cant have designated initializer inside the subclass because I want to call the convenience init of superclass. But my convenience init is also throwing complier error.
Here's the code:
enum PushNotificationCategoryIdentifier:String {
}
convenience init(categoryIdentifier:PushNotificationCategoryIdentifier, actions:[UNNotificationAction], intentIdentifiers:[String], options: UNNotificationCategoryOptions) {
self.init(identifier: categoryIdentifier.rawValue, actions: actions, intentIdentifiers: intentIdentifiers, options: options)
}
This is resulting in error: self.init isn't called on all paths before returning from initializer
I guess this is because this class is implemented in Objective-C and may be they have not called the designated initailizer from convenience initailizer(as Objective-C classes dont have to call designated initializer from convenience initailizer).
But does that mean I can't subclass UNNotificationCategory if I want to write an initializer in it?
No you can do this. You will have to define init() method for this. Right now you have only defined convenience init(). But you will have to define init()in your subclass.
When you write a convenience init() it is only there to help initialization in an easy way but still you will have to call designated init with syntax init() from the convenience init().
You can read it on Apple Official Documentation

Why is the superclass designated initializer getting called by default? [duplicate]

This question already has an answer here:
Why doesn't Swift force my designated initializer to call super?
(1 answer)
Closed 6 years ago.
I am new to Swift and getting some problem regarding initializers. I have created a Swift file with the following code :
import Foundation
class SuperClass
{
var a : Int
init()
{
a = 10
print("In Superclass")
}
}
class SubClass : SuperClass
{
override init()
{
print("In Subclass")
}
}
In the above code, init() of SubClass does not contain call to init() of SuperClass i.e. there is no super.init() in SubClass init().
So my question is:
1. Why is it not giving any error if I don't call designated init() of SuperClass
2. If I am creating an object of SubClass i.e. let s = SubClass(), the output is :
In Subclass
In Superclass
Why the init() of SuperClass is getting called? Does a subclass init() calls the superclass init() by default?
As far as I understood your question, you're not only wondering why, when and how the initializer gets called automatically, but also complaining about the missing documentation of this behavior.
First of all I agree with you on the lack of documentation - just like you I'm not able to find anything about this behavior and therefore it should be added to the documentation by Apple.
Why super.init() is called:
As per documentation a designated initializer of the superclass has to be called by a designated initializer of its subclass in order to fully initialize all properties.
Rule 1
A designated initializer must call a designated initializer from its
immediate superclass.
Your code example above proves it's obviously done implicitly: print("In Superclass") prints to the console, so super.init() is somehow invoked while creating an instance.
When and how super.init() is called:
There are some conditions to be met in order to allow the compiler to call the designated initializer of the superclass implicitly:
The superclass must have only one designated initializer which
is then called. Otherwise the compiler had to choose one to delegate
to. This single designated initializer could be also the default
initializer or an inherited initializer.
class SuperClass {
var a: Int
init() {
a = 10
}
// introduction of a second designated initializer in superclass:
init(withValue value: Int) {
a = value
}
}
class SubClass: SuperClass {
// won't compile:
// "error: super.init isn't called on all paths before returning from initializer"
override init() {}
}
The single designated initializer of the superclass mustn't have any
parameters. After all the compiler wouldn't know any appropriate
parameter to be passed.
class SuperClass {
var a: Int
// declaration of an initializer with parameter:
init(withValue value: Int) {
a = value
}
}
class SubClass: SuperClass {
// won't compile:
// "error: super.init isn't called on all paths before returning from initializer"
override init() {}
}
The designated initializer of the subclass mustn't further read or
modify (inherited) instance properties of the superclass or call
instance methods of the superclass. That's because of Swift's
two-phase initialization process with its corresponding safety
checks and the fact that the implicit delegation up to the designated
initializer of the superclass happens at the end of the
init-Statement in the subclass.
Safety check 2
A designated initializer must delegate up to a
superclass initializer before assigning a value to an inherited
property. If it doesn’t, the new value the designated initializer
assigns will be overwritten by the superclass as part of its own
initialization.“
Safety check 4
An initializer cannot call any instance methods, read
the values of any instance properties, or refer to self as a value
until after the first phase of initialization is complete.
class SuperClass {
var a: Int
init() {
a = 10
}
}
class SubClass: SuperClass {
// won't compile:
// "error: use of 'self' in property access 'a' before super.init initializes self"
override init() {
a = 10 // modifying inherited self.a before phase 1 of initialization completes isn't valid!
// implicit delegation to super.init()
}
}
Safety check 1
A designated initializer must ensure that all of the
properties introduced by its class are initialized before it
delegates up to a superclass initializer.
class SuperClass {
var a: Int
init() {
a = 10
}
}
class SubClass: SuperClass {
// introduction of instance property "b"
var b: Int
// compiles finely:
override init() {
b = 10 // initializing self.b is required before delegation!
// implicit delegation to super.init()
}
}
I hope that helps.
Why the init() of SuperClass is getting called? Does a subclass init() calls the superclass init() by default?
Basically, yes.
If all the rules say that you should say super.init() and you don't say it, it is called for you.
I don't like this behavior; it is poorly documented, and besides, secretly doing stuff for you seems against the spirit of Swift. But I filed a bug against it long ago and was told it was intended behavior.
Every class have at least one designated initializer which is responsible for initializing instance variables.
Here is an extract from the doc :
Classes tend to have very few designated initializers, and it is quite common for a class to have only one. Designated initializers are “funnel” points through which initialization takes place, and through which the initialization process continues up the superclass chain.
Every class must have at least one designated initializer. In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass, as described in Automatic Initializer Inheritance below.
You can refer to the complete documentation for further details : https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html
A rule of thumb
Create one designated initializer for your class.
Call the designated initializer of the superclass or let the system figure this out for you.
Create zero or more convenience initializers which will call your designated initializer.
You are not accessing the super class variable in the subclass hence super.init() is called followed by the subclass's init. But if you were to try using the super class variable in subclass without calling its initialiser then it will result in a compile time error.
I didn't try this myself, but the Swift Language Guide says:
Initializer Delegation for Class Types
To simplify the relationships between designated and convenience initializers, Swift applies the following three rules for delegation calls between initializers:
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.
A simple way to remember this is:
Designated initializers must always delegate up.
Convenience initializers must always delegate across.
So, as it is a 'rule' to call super.init(), it might just be done internally, if not implemented explicitly.

UIBezierPath Subclass Initializer

I'm trying to create a subclass of UIBezierPath to add some properties that are useful to me.
class MyUIBezierPath : UIBezierPath {
var selectedForLazo : Bool! = false
override init(){
super.init()
}
/* Compile Error: Must call a designated initializer of the superclass 'UIBezierPath' */
init(rect: CGRect){
super.init(rect: rect)
}
/* Compile Error: Must call a designated initializer of the superclass 'UIBezierPath' */
init(roundedRect: CGRect, cornerRadius: CGFloat) {
super.init(roundedRect: roundedRect, cornerRadius: cornerRadius)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
EDIT:
I need this because in my code i write
var path = MyUIBezierPath(roundedRect: rect, cornerRadius: 7)
and it results in a compile error:
"Must call a designated initializer of the superclass 'UIBezierPath'"
I tried to add that initializers in the subclass but it seems not to work.
Can you help me please?
NOTE: This problem is solved in iOS 9, where the API has been rewritten so that init(rect:) exists, and so do all the others, as convenience initializers, as they should be.
The Problem
In a nutshell, the problem you're experiencing is that the following code does not compile:
class MyBezierPath : UIBezierPath {}
let b = MyBezierPath(rect:CGRectZero)
From the Swift point of view, that seems just wrong. The documentation appears to say that UIBezierPath has an initializer init(rect:). But then why isn't UIBezierPath's init(rect:) being inherited in our subclass MyBezierPath? According to the normal rules of initializer inheritance, it should be.
The Explanation
UIBezierPath is not intended for subclassing. Accordingly, it doesn't have any initializers - except for init(), which it inherits from NSObject. In Swift, UIBezierPath looks as if it has initializers; but this is a false representation. What UIBezierPath actually has, as we can see if we look at the Objective-C headers, are convenience constructors, which are class methods, such as this:
+ (UIBezierPath *)bezierPathWithRect:(CGRect)rect;
Now, this method (along with its siblings) demonstrates some unusual features that Swift does not deal with very well:
It is not merely a variant of an initializer; it is a pure convenience constructor. Objective-C shows us that UIBezierPath has no corresponding true initializer initWithRect:. That's a very unusual situation in Cocoa.
It returns UIBezierPath*, not instancetype. This means that it cannot be inherited, because it returns an instance of the wrong type. In a subclass MyBezierPath, calling bezierPathWithRect: yields a UIBezierPath, not a MyBezierPath.
Swift copes badly with this situation. On the one hand, it translates the class method bezierPathWithRect: into an apparent initializer init(rect:), in accordance with its usual policy. But on the other hand, this is not a "real" initializer, and cannot be inherited by a subclass.
You have thus been misled by the apparent initializer init(rect:) and then surprised and stumped when you could not call it on your subclass because it isn't inherited.
NOTE: I'm not saying that Swift's behavior here is not a bug; I think it is a bug (though I'm a little hazy on whether to blame the bug on Swift or on the UIBezierPath API). Either Swift should not turn bezierPathWithRect: into an initializer, or, if it does make it an initializer, it should make that initializer inheritable. Either way, it should be inheritable. But it isn't, so now we have to look for a workaround.
Solutions
So what should you do? I have two solutions:
Don't subclass. Subclassing UIBezierPath was a bad idea to start with. It is not made for this sort of thing. Instead of a subclass, make a wrapper - a class or struct that, rather than having the feature that is a UIBezierPath, has the feature that it has a UIBezierPath. Let's call it MyBezierPathWrapper:
struct MyBezierPathWrapper {
var selectedForLazo : Bool = false
var bezierPath : UIBezierPath!
}
This simply couples your custom properties and methods with a normal UIBezierPath. You could then create it in two steps, like this:
var b = MyBezierPathWrapper()
b.bezierPath = UIBezierPath(rect:CGRectZero)
If that feels unsatisfactory, you can make this a one-step creation by adding an initializer that takes the UIBezierPath:
struct MyBezierPathWrapper {
var selectedForLazo : Bool = false
var bezierPath : UIBezierPath
init(_ bezierPath:UIBezierPath) {
self.bezierPath = bezierPath
}
}
And now you can create it like this:
var b = MyBezierPathWrapper(UIBezierPath(rect:CGRectZero))
Subclass with a convenience constructor. If you insist on subclassing, even though UIBezierPath is not intended for that sort of thing, you can do it by supplying a convenience constructor. This works because the only important thing about a UIBezierPath is its CGPath, so you can make this convenience constructor a copy constructor merely transferring the path from a real UIBezierPath:
class MyBezierPath : UIBezierPath {
var selectedForLazo : Bool! = false
convenience init(path:UIBezierPath) {
self.init()
self.CGPath = path.CGPath
}
}
Now we can create one very similarly to the previous approach:
let b = MyBezierPath(path:UIBezierPath(rect:CGRectZero))
It isn't great, but I think it's marginally more satisfying than having to redefine all the initializers as your solution does. In the end I'm really doing exactly the same thing you're doing, in a more compressed way. But on balance I prefer the first solution: don't subclass in the first place.
I found this simple workaround that seems to do its job.
class MyUIBezierPath : UIBezierPath {
var selectedForLazo : Bool! = false
override init() {
super.init()
}
init(rect: CGRect){
super.init()
self.appendPath(UIBezierPath(rect: rect))
}
init(roundedRect: CGRect, cornerRadius: CGFloat) {
super.init()
self.appendPath(UIBezierPath(roundedRect: roundedRect, cornerRadius: cornerRadius))
}
required init(coder aDecoder: NSCoder) {
super.init()
}
}
Please post other solutions because they'll surely be better than mine.

Swift: odd XCode behaviour with required initializer in subclass of NSArray

Issue background: I have a class that is subclass of NSArray, not directly but it is.
Structure is more or less like this MyClass -> Class1 -> Class2 -> NSArray.
Everything except MyClass is in Objective-C, MyClass is in Swift. It worked well until I upgraded to Yosemite and Xcode 6.1.
Now, during compilation it throws an error
'required' initializer 'init(arrayLiteral:)' must be provided by subclass of 'NSArray'
Which is pretty odd because there are other classes, siblings to MyClass without compilator complaining about them.
When I add the initializer,
required convenience init(arrayLiteral elements: AnyObject...) {
fatalError("not implemented")
}
XCode throws another error saying Declarations from extensions cannot be overridden yet.
Does anybody have any idea what can I do? Code has zero changes at all.
You might just not see the compilation errors in the other files since compilation tries to stop at the first problematic file.
If you don't specify any designated initializers in your subclass or if you override all designated initializers but no convenience initializers, the problematic initializer is inherited automatically. In that case it compiles fine:
class MyArray1: NSArray {
override init() { fatalError("todo") }
override init(objects: UnsafePointer<AnyObject?>, count cnt: Int) { fatalError("todo") }
required init(coder aDecoder: NSCoder) { fatalError("todo") }
}
class MyArray2: MyArray1 {
}
This compiles just fine on Xcode 6.1.
See Automatic Initializer Inheritance in The Swift Programming Guide.

Using superclass's convenience initializers in subclass instantiation

This occurred in a playground in XCode6-Beta5
I'm trying to add some functionality to SKSpriteNode in a subclass, Sprite, but I still want to use the convenience initializers in SKSpriteNode. However, I get the following error:
"Cannot convert the expression's type 'Sprite' to type 'Sprite'"
from this code:
import Cocoa
import SpriteKit
class Sprite : SKSpriteNode {
// The following code makes this not compile
// required init(coder aDecoder: NSCoder!) {
// super.init(coder: aDecoder)
// }
}
var sprite = Sprite(imageNamed: "Rayman1.png") // Error occurs on this line
The convenience initializer in question is declared like this:
convenience init(imageNamed name: String!)
What am I doing wrong?
Initializer Chaining rules as specified in the Swift Programming Guide, which reads:
Designated initializers must call a designated initializer from their immediate superclass.
If you are calling superclass's convenience initialize in sub class instantiation then this is not allowed.
Hope this helps.. :)
I had to get rid of the init(coder aDecoder: NSCoder!) method, and that fixed everything. I have no idea why.

Resources