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

Time:01-04

From: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

Create convenience initializers whenever a shortcut to a common initialization pattern will save time or make initialization of the class clearer in intent.

My understanding of convenience init is basically as a shortcut to "typical" input parameters. Most of the time, the number of parameters in convenience init() is the same as required init()

eg:

public required init() {
    super.init()
    
    self.$timeStamp.owner = self
    self.$position = self
    self.$distance = self
}

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

I believe there are instances where convenience init will have lesser # of parameters vs required init as some default values will be put in place.

I think the below is valid? There is an additional parameter in required init but is not present in convenience init. However, in this case, how am I able to access and pass a value to the speed parameter?

public required init() {
    super.init()
    
    self.$timeStamp.owner = self
    self.$position = self
    self.$distance = self
    self.$speed = self // ADDED THIS
}

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

CodePudding user response:

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.

  •  Tags:  
  • Related