Differencebetween Convenience Init VS Init in Swift, Explicit Examples Better

What is the difference between convenience init vs init in swift, explicit examples better

Standard 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.

per the Swift Documentation

In a nutshell, this means that you can use a convenience initializer to make calling a designated initializer faster and more "convenient". So convenience initializers require the use of self.init instead of the super.init you might see in an override of a designated initializer.

pseudocode example:

init(param1, param2, param3, ... , paramN) {
// code
}

// can call this initializer and only enter one parameter,
// set the rest as defaults
convenience init(myParamN) {
self.init(defaultParam1, defaultParam2, defaultParam3, ... , myParamN)
}

I use these a lot when creating custom views and such that have long initializers with mainly defaults. The docs do a better job explaining than I can, check them out!

What the difference between designated and convenience init in this code below

The difference arises in subclasses; in what you've shown there is no usage difference.

“Convenience initializers must always delegate across” meaning in class A a convenience initializers calls a designated initializers in class A. “Designated initializers must always delegate up” meaning class B, a subclass of A, calls designated initializers in A.

Thus, in your 'part 2' both of the initializers in Food are available to subclasses of Food. But, in your 'part 1', only one initializer can be used in subclasses.

For example (Food in the following is your 'part 1'):

 21> class Fat : Food { 
22. init () {
23. super.init()
24. }
25. }
repl.swift:23:9: error: must call a designated initializer of the superclass 'Food'
super.init()
^

Quotes are: Excerpt From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/us/jEUH0.l

Does Convenience Init need to have same number of parameters as Required Init

There is no correlation in number of parameters between designated (or even required) initializers and convenience initializers.

Convenience initializers are simply those that need to forward initialization to one of designated initializers. How many input parameters will it have is completely dependent on implementation and use-case. It may be more, less or equal.

It is hard to find a simple-enough example to demonstrate all of those but consider something like this:

class HighlightedWordContainer {

let words: [String]
var highlightedWord: String

init(words: [String], highlighted: String) {
self.words = words
self.highlightedWord = highlighted
}

init(words: [String], highlightedIndex: Int) {
self.words = words
self.highlightedWord = words[highlightedIndex]
}

convenience init(singleWord: String) {
self.init(words: [singleWord], highlighted: singleWord)
}

convenience init(word1: String, word2: String, word3: String, highlighted: String) {
self.init(words: [word1, word2, word3], highlighted: highlighted)
}

convenience init(wordsSeparatedByWhitespace: String, highlightedIndex: Int) {
let words = wordsSeparatedByWhitespace.components(separatedBy: .whitespaces)
self.init(words: words, highlightedIndex: highlightedIndex)
}

convenience init?(descriptor: [String: Any], keys: [String], selectedKey: String) {
let words: [String] = keys.compactMap { descriptor[$0] as? String }
guard words.isEmpty == false else { return nil }

guard let selectedWord = descriptor[selectedKey] as? String else { return nil }
guard let selectedWordIndex = words.firstIndex(of: selectedWord) else { return nil }

self.init(words: words, highlightedIndex: selectedWordIndex)
}

}

Here I created a class with 2 designated initializers. These two need to set all properties that are not already set by default. Which means they need to set both words and highlightedWord. A designated initializer may not delegate a call to another designated initializer so the following will not work:

init(words: [String], highlightedIndex: Int) {
self.init(words: words, highlighted: words[highlightedIndex])
}

And all convenience initializers do need to call any of the designated initializers and may also not directly set or use self properties UNTIL a designated constructor is being called. So:

convenience init(wordsSeparatedByWhitespace: String, highlightedIndex: Int) {
let words = wordsSeparatedByWhitespace.components(separatedBy: .whitespaces)
// print(highlightedWord) // NOT OK!
self.init(words: words, highlightedIndex: highlightedIndex)
print(highlightedWord)
}

And from the example I hope it makes clear that a convenience initializer can have more, fewer or same number of input parameters. It all just depends.

Some more plausible examples:

class Point {
let x: Int
let y: Int

init(x: Int, y: Int) { self.x = x; self.y = y }
convenience init(x: Int) { self.init(x: x, y: 0) }
convenience init(y: Int) { self.init(x: 0, y: y) }
convenience init(polar: (radius: Double, angle: Double)) { self.init(x: Int(cos(polar.angle)*polar.radius), y: Int(sin(polar.angle)*polar.radius)) }
}

class NumericValueAsString {
let stringValue: String // A value represented as "123.456"

init(stringValue: String) { self.stringValue = stringValue }
convenience init(value: Int) { self.init(stringValue: .init(value)) }
convenience init(integerPart: String, fractionPart: String) { self.init(stringValue: integerPart + "." + fractionPart) }
}

Also; required keyword has nothing to do with anything in this context. You can place it to any of the initializers (designated or convenience), to multiple of them or even all of them.

Adding additional info based on a comment:

your examples shows that you can have a convenience init that can pass
parameters back to the required init``. what about instances where I
there is no convenience init``` that can pass a value to parameter to
one which is in required init but not in the convenience init. Like my
speed parameter in my original question.

This really depends on the interface of your class which is not fully shown in your example. For instance, is speed publicly exposed?

If speed is public then what you are looking for is something along the lines

let itemInstance = MyItem()
itemInstance.timeStamp = timeStamp
itemInstance.position = position
itemInstance.distance = distance
itemInstance.speed = speed

or

let itemInstance = MyItem(timeStamp: timeStamp, position: position, distance: distance)
itemInstance.speed = speed

or even create a new convenience constructor

extension MyItem {
convenience init(timeStamp: FitTime? = nil,
position: Position? = nil,
distance: Measurement<UnitLength>? = nil
speed: Speed? = nil
) {
self.init(timeStamp: timeStamp, position: position, distance: distance)
self.speed = speed
}
}

let itemInstance = MyItem(timeStamp: timeStamp, position: position, distance: distance, speed: speed)

But if your speed property is privately defined than you can not assign to it anywhere but internally. You need to be able to modify your class so that it can accept this property somewhere. Either via constructor, via property or via a method. Until there is no access at all you can not expect to be able to modify it.

But if this is your own class I would turn things upside down if possible. Your main constructor should be the one that accepts all 4 parameters. But your convenience initializer should be required (Yes, convenience initializer may be required). So in that case you would have:

public required convenience init() {
self.init(timeStamp: self, position: self, distance: self, speed: self)
}

public init(timeStamp: FitTime? = nil,
position: Position? = nil,
distance: Measurement<UnitLength>? = nil,
speed: Speed?) {
super.init()

self.timeStamp = timeStamp
self.position = position
self.distance = distance
self.speed = speed
}
}

This specific case feels like it is missing something compared to your code but again, you did not provide full definition of your class and with it does or is supposed to do. For instance in the code you provided if one would call a convenience initializer then the following lines would execute in this order:

self.$timeStamp.owner = self // from self.init()
self.timeStamp = timeStamp

which looks like a problem to begin with.

So to wrap it up. There are very many ways you can achieve what you are asking for. But it all depends on what you are trying to build here.

I hope it at least puts you in the right track.

self.init() causes convenience init(){ } to run infinitely

You have found a little conflict between the Objective-C and Swift implementation.

CBPeripheralManager has 3 init methods which basically work like this:

convenience init() {
self.init(delegate: nil, queue: nil)
}

convenience init(delegate: CBPeripheralManagerDelegate?, queue: DispatchQueue?) {
self.init(delegate: delegate, queue: queue, options: nil)
}

// designated
init(delegate: CBPeripheralManagerDelegate?, queue: DispatchQueue?, options: [String : Any]?)
// the actual implementation
}

Once you have declared a new convenience initializer, the two convenience initializers get removed and only the one defined by you exists. Normally, you wouldn't be able to call Peripheral.init() at all. However, the problem is that Objective-C classes always have an init method without parameters.

That means that init() delegates to your new convenience initializer and your initializer delegates to init() again, ending up in an infinite loop.

Solution, don't call init(). Call the designated initializer:

class Peripheral: CBPeripheralManager, CBPeripheralManagerDelegate {
var peripheralManager : CBPeripheralManager!

convenience init(delegate: CBPeripheralManagerDelegate?, queue: DispatchQueue?) {
print("howdy")
self.init(delegate: delegate, queue: queue, options: nil)
peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: [CBPeripheralManagerOptionShowPowerAlertKey: true])
peripheralManager.add(myCBService)
}
}

However, there seems to be a deeper problem. Why do you create a new CBPeripheralManager from inside a CBPeripheralManager?

Isn't what you want actually the following?:

class Peripheral: NSObject, CBPeripheralManagerDelegate {
var peripheralManager: CBPeripheralManager!
override init() {
print("howdy")

super.init()

peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: [CBPeripheralManagerOptionShowPowerAlertKey: true])
peripheralManager.add(myCBService)
}

func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
}
}

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

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.

Overriding convenience init

Override the designated initialisers:

class MyTextView : NSTextView {

init(frame frameRect: NSRect, textContainer aTextContainer: NSTextContainer!) {
super.init(frame: frameRect, textContainer: aTextContainer)

setup();
}

func setup() {
...
}
}

var textView = MyTextView(frame: NSRect())

Since all the designated initialisers are overridden, all convenience will be automatically inherited.

There are two other designated initialzers to override:

init() {
}

and

init(coder:) {
}


Related Topics



Leave a reply



Submit