Using superclass's convenience initializers in subclass instantiation - ios

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.

Related

Swift: Required initializer giving me an error message

I have a class named Alarm inheriting from NSObject, and in it, I have a property I'm having an issue with, alarmLastTriggeredDate:
class Alarm: NSObject {
var alarmLastTriggeredDate: NSDate
override init() {
super.init()
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(alarmLastTriggeredDate, forKey: "alarmLastTriggeredDate")
}
required init(coder aDecoder: NSCoder) {
if let alarmLastTriggeredDateDecoded = aDecoder.decodeObjectForKey("alarmLastTriggeredDate") as? NSDate
{
alarmLastTriggeredDate = alarmLastTriggeredDateDecoded
}
}
}
I'm new to Swift, and not sure why I'm getting the following errors:
#override init: Property 'self.alarmLastTriggeredDate' not initialized at super.init call
#required init: Property 'self.alarmLastTriggeredDate' not initialized at implicitly generated super.init call
It seems the only way to fix this problem is to initialized it in both places, but that's redundant code, and seems wrong. Am I missing something?
The compiler must be sure that every non optional property is successfully initialized:
before a call to a super init is performed
AND before the object initialisation has been completed
That's why you need to populate alarmLastTriggeredDate inside bot initializers.
And no, it's not redundant code since someone could use one of the 2 initializers to create your Alarm object.

UITableViewCell and Cannot invoke 'init' with an argument list of type '(coder: NSCoder)'

So Ive built a custom cell class for my UITableView. Xcode insists that this UITableViewCell has the following initializer (Xcode actually auto-fills this code for me):
class customCell: UITableViewCell {
required init(coder aDecoder:NSCoder) {
fatalError("init(coder:) has not been implemented")
}
Ive modified the contents of this initializer to contain the following:
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
If I remove the keyword required then Xcode kicks out an error and wants to correct me by putting the keyword back in.
So in my UITableView class I try to register this cell like so:
override func viewDidLoad(){
super.viewDidLoad()
let coder: NSCoder = NSCoder()
let customCell: AnyClass = customCell(coder: coder)
table?.registerClass(customCell, forCellReuseIdentifier:"cell")
The penultimate line (the one where I initialize the customCell with the NSCoder, as Xcode wants me to) produces the error:
Cannot invoke 'init' with an argument list of type '(coder: NSCoder)'
If I can I'd prefer to build my own initializer without an NSCoder.
If I try to use any other initializer then Xcode kicks out an error along the lines of:
Extra argument 'reuseIndenifier' in call
or
Missing argument in call
It will continue complaining until I am using the initializer with NSCoder. I am forced into using this initializer.
Please correct me if I am way off here, but to me it seems like I am caught in an catch 22 situation - Xcode complains if I build an initializer without a coder, and then when I try to use an initializer with a coder it complains that I am using a coder.
Despite this apparent contradiction I am fairly convinced all of this is a result of me being a dumbass somewhere.
Would anyone out there be kind enough to point me in the right direction?
You need to register the class itself not an instance of it.
eg.
tableView?.registerClass(customCell.self, forCellReuseIdentifier:"cell")
The issue with requiring init(coder aDecoder:NSCoder) is a separate one and discussed here: 'required' initializer 'init(coder:)' must be provided by subclass of 'UITableViewCell'`

Changes on NSCoding protocol

I just came back on the development of a Swift application for iOs that I started back on August. It seems that a lot of changes happened. Now my NSCoding protocol is not valid anymore and I cannot find what's wrong. Actually I do but I don't know how to implement my old code with the new protocol. Here is my old init(coder: NSCoder) :
required init(coder: NSCoder!) {
if (coder != nil) {
//Variables initialization
}
else {
//Defaults values
}
super.init()
}
Actually, I cannot use NSCoder! anymore so I cannot check if there is a coder so if I have to set default values or not. The question is : When coder is nil, what initializer is called ? Is it init or init(coder: NSCoder!) ? If it's the second one, how to do what I use to do (with the code above) ?
With the updated protocol it is illegal for a Swift class implementing NSCoding to receive nil in initWithCoder. This should simplify your code since a nil coder is an exceptional case and the program should have taken an alternative path when it couldn't create the decoder.
If you really have two paths you can break your init into two methods:
override init() {
//Variables initialization
super.init()
}
required init(coder: NSCoder) {
//Defaults values
super.init()
}
This does have implications for Objective-C code consuming this class since passing nil will crash the app. Swift code will be safe since it will generate a compilation error is you pass an optional.

Getting Initialiser error in iOS Swift

I am working on Apple Swift Programming . I am taking a variable that is initialised in ViewDidLoad( ) method. But it shows an error like "*Class ABc has no initializer *" . After much searching about this, I got a suggestion to use init() method to initialise the variable which you want use. Yet still, I am getting an error like this .
I am not able to make out where am I going wrong here. Kindly guide me on this.
Thanks in advance.
Super.init isn't called before returning from initializer
Error is pretty much clear. You are not calling the super class init(). But for UIViewController you should use any designated initialzer i guess. Try to use init(coder aDecoder: NSCoder!)
init(coder aDecoder: NSCoder!)
{
super.init(coder: aDecoder)
// Your intializations
}

Adding Convenience Initializers in Swift Subclass

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.

Resources