Unable to add a convenience initialiser to an SKShapeNode subclass - swift

Trying to add a convenience init to an SKShapeNode subclass generates two compiler errors:
class Shape : SKShapeNode {
convenience init(diameter: CGFloat) {
self.init(circleOfRadius: diameter / 2) // error: Use of 'self' in delegating initializer before self.init is called
} // error: Self.init isn't called on all paths in delegating initializer
}
Yet, if you paste the following code into a playground, it compiles and executes as expected:
import CoreGraphics
import SpriteKit
class FakeShapeNode : SKNode {
convenience init(circleOfRadius radius: CGFloat) {
self.init()
}
}
class FakeShape : FakeShapeNode {
convenience init(diameter: CGFloat) {
self.init(circleOfRadius: diameter / 2)
}
}
let shape = FakeShape(diameter: 3)
The question is, what could be the relevant difference here between FakeShapeNode and SKShapeNode?
Edit
As suggested by #Knight0fDragon, below a reminder of the relevant initialisation rules may be helpful:
Assuming that you provide default values for any new properties you
introduce in a subclass, the following two rules apply:
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.
These rules apply even if your subclass adds further convenience initializers.

You can't have a convenience init call the super's convenience init, it has to be a delegating initializer. Unfortunately there is a bug in playgrounds that will allow it to work, but in applications, you will receive the error you are seeing.
To understand how convenience inits work, please see https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/Initialization.html

Related

Swift SKPhysicsBody empty subclass doesn't inherit parent inits

While trying to inherit from SkPhysicsBody I found something rather strange :
None of the convenience init are inherited!
Here is the code you can test in a playground :
import PlaygroundSupport
import SpriteKit
import GameplayKit
public class SKPhysicsBodySubClass:SKPhysicsBody
{
}
let pb = SKPhysicsBody(circleOfRadius: 10)
let pbsc = SKPhysicsBodySubClass(circleOfRadius: 10)
The last instruction produce an error suggesting that the parent init doesn't exist : "Incorrect argument label in call (have 'circleOfRadius:', expected 'coder:')"
I'm also aware of the rule of inheritance :
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.
Any idea what's going on here? what did I miss?

Decodable forces super open class to implement the initializer

I got this code:
open class A: Decodable {
public init() {
}
}
open class B: A {
public override init() {
super.init()
}
open required init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
}
And the almighty Xcode 9.4.1 tells me I need to change open to public before the requiered keyword. After I change open to public, the compiler tells me I need to change it to open. I can't get it to work while both classes are open, without my super class A explicitly implementing the required initializer as seen in class B. Why?
Decodable forces super open class to implement the initializer
If you don't inherit the required initializer the superclass has then you have to implement it yourself.
Required Initializers
Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer: <..>
You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain.
You do not have to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initializer.
How you can avoid implementing it yourself:
Initializer Inheritance
Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Sources
Initialization #Required Initializers
Initialization #Automatic Initializer Inheritance

Understanding init() when sub-classing in Swift

How can I add a custom initializer when inheriting from a class that already has an initializer?
What I have is a Vehicle class which has an itializer who takes an argument name. What I want to do is inherit from this Vehicleclass, create another initializer for the new class but keep using the existing initializer.
Base Class (No problem here):
class Vehicle{
var make:String
init(make:String){
self.make = make
}
}
New Class (Doesn't work):
// Not sure how to structure this class
class Car:Vehicle {
var engine:Double
override init(){
super.init()
}
init(engine:Double){
self.engine = engine
}
}
This is what I would like to be able to do... re-use the existing initializer plus the new one.
let cobalt = Car(make:"Chevy" engine: 2.5)
Any designated initializer in a subclass must call a designated initializer from its immediate superclass:
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
...
From the Swift Language Guide - Initialization.
Hence, you could construct your designated initializer of Car to take two arguments, make and engine, and use the latter to inititalize the member property engine of the subclass, thereafter call the designated initializer of the superclass using the supplied make parameter (supplied to subclass initializer) as argument to the superclass initializer.
class Car: Vehicle {
var engine: Double
init(make: String, engine:Double){
self.engine = engine
super.init(make: make)
}
}

In Swift, how do I create a convenience init for a class where the init's implementation creates the class value instead of calling an existing init

I feel like the answer is obvious, but I haven't been able to figure this out and it seems to be a recurring problem for me. Basically I want to do something like this:
extension NSData {
convenience init(JSONObject: AnyObject) {
do {
self = try NSJSONSerialization.dataWithJSONObject(JSONObject, options: [])
}
catch {
self = nil
}
}
}
However it won't let me simply assign a value to self. I do this all the time with enums, but it won't let me do it with classes. Is there any way implement at convenience initializer using an instance of the class created in the initializer implementation?
Saying that factory initializers are "not supported yet" in Swift is fallacious. Their exclusion is a design decision, and their use intended to be covered by failable initializers; quoting the following Apple Swift blog post
Failable initializers eliminate the most common reason for factory
methods in Swift, which were previously the only way to report failure
when constructing this object.
...
Using the failable initializer allows greater use of Swift’s uniform
construction syntax, which simplifies the language by eliminating
the confusion and duplication between initializers and factory
methods.
So in your case, you're probably looking for a convenience failable initializer. E.g., something along the lines
extension NSData {
convenience init?(JSONObject: AnyObject) {
do {
let foo = try NSJSONSerialization.dataWithJSONObject(JSONObject, options: [])
self.init(data: foo)
}
catch {
return nil
}
}
}
/* Example usage */
let foo : AnyObject = ["Foo":"bar"]
let bar = NSData.init(JSONObject: foo)
In the title of your question you include "... instead of calling an existing init". When making use of convenience initializer, a designated initializer (of same class) must be called at some point (even via other convenience initializers). From the Swift Language Guide - Initialization - Class Inheritance and Initialization:
...
Rule 2
A convenience initializer must call another initializer from the same
class.
Rule 3
A convenience initializer must ultimately call a designated
initializer.
The example code above, however, allows an early escape (failure) of the convenience initializer if NSJSONSerialization.dataWithJSONObject(...) fails, but if it succeeds, sooner or later a designated initializer needs to be called (in this case init(data:) designated initializer).
For details on failable initializers, see the Swift Language Guide - Initialization - Failable Initializers. For an additional remark regarding the initializer chain (convenience -> ... -> designated initializer), see rickster:s comment below.
If I understand you right you want a factory initializer. In Swift they are not supported yet. The best you could do is to use static factory method.

Meaning of the convenience keyword in Swift

When I go back to look at some of Apple's Sprite Kit documentation I see a lot of occasions where a keyword called convenience comes up. For Example
convenience init(texture texture: SKTexture?, size size: CGSize)
What does it mean?
Convenience initialisers allow you to initialise a class without all the required parameters the designated initialiser needs.
For example, in a very basic example you may have a designated initialiser for a class that requires a String:
init someName(value: String) {
You could also create a convenience initialiser to go along side this that takes an int and converts it to a String and then calls the designated initialiser and passes it that String. This way if your class is initialised and passed an int instead or by mistake, it won't error and will handle it.
convenience init someName2(value: Int) {
let someString = String(value)
someName(value: someString)
}
Another use for them is that the designated initialiser may take multiple parameters. You could create a convenience initialiser to go along side this that takes only one of those parameters, creates the others and sets them to some default values and then calls the designated initialiser, passing them all in. This way you do not need to specify all required parameters of the designated initialiser since if you don't, your convenience initialiser will fill in missing ones with default values.
convenience init someName(value: String) {
someName(value: someString, value2: "DefaultValue")
}
init someName(value: String, value2: String) {
That is a convenience initializer.
From the docs:
Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type.
An initializer that is not marked convenience is a designated initializer:
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
The docs I linked above are very detailed, because there is a lot to initialization. You'll need to read them to really understand what's going on. But to give an example, let's say you create a car class:
class Car {
let numberOfWheels: Int
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
convenience init() {
self.init(numberOfWheels: 4)
}
}
The car class contains two initializers - a designated initializer and a convenience initializer.
The designated initializer is simply marked init. Inside a designated initializer, you are required to set all class properties so that the class is ready to go once initialization finishes. We set numberOfWheels inside our initializer.
The convenience initializer is marked convenience init. A convenience initializer may do whatever, but it must call one of the designated initializers before it finishes. In our example, it calls self.init(numberOfWheels: Int) and supplies a default number of wheels (4).
Convenience initializers are there exactly for their namesake: convenience. They allow you to set up initializers to classes that deal with common initialization cases. In our Car class, it is typical for a car to have four wheels, so our convenience initializer makes that common case easier.
To create a car, we can use either the designated initializer or the convenience initializer:
let carA = Car(numberOfWheels: 3) // makes a car with 3 wheels
let carB = Car() // makes a car with 4 wheels
There are a lot more rules around initialization, especially in class hierarchies where super.init and initializer inheritance must be considered as well. I cannot describe them all any better or more succinctly than the official documentation, so again, I suggest you check there.
Default init:
Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.
convenience init:
Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type.
as per the Swift Documentation
Actually - Swift defines two kinds of initializers.
Designated initializers
Convenience initializers
Designated initializers are the primary initializers for a class. Every class should have at least one designated initializer.
init(parameters if any) {
}
Convenience initializers are secondary, supporting initializers for a class. Convenience initializers are written in the same style, but with the convenience modifier placed before the init keyword, separated by a space:
convenience init(parameters if any) {
}
Convenience init initializes the designated init method by calling self.init.
Example:
class HumanBeing {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: “not set”)
// Convenience init call the designated init method
}
}
let humanBeingObj1 = HumanBeing() // calls convenience init
let humanBeingObj2 = HumanBeing(name: “abhilash”) // calls designated init