Convenience Init 是否需要与 Required Init 具有相同数量的参数

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

发件人: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.

我对 convenience init 的理解基本上是作为“典型”输入参数的快捷方式。大多数时候,convenience init()中的参数个数与required init()

中的参数个数相同

例如:

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
    }
}

我相信在某些情况下 convenience init 的参数数量会比 required init 少,因为一些默认值会到位。

我认为以下内容有效? required init 中有一个附加参数,但 convenience init 中没有。但是,在这种情况下,我如何访问 speed 参数并将值传递给 speed 参数?

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
    }
}

指定(甚至是必需)初始化器和便利初始化器之间的参数数量没有相关性。

便利初始化器只是那些需要将初始化转发给指定初始化器之一的初始化器。它有多少输入参数完全取决于实现和用例。可能大于、小于或等于。

很难找到一个足够简单的例子来演示所有这些,但请考虑这样的事情:

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[[=10=]] 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)
    }
    
}

这里我创建了一个 class 和 2 个指定的初始值设定项。这两个需要设置默认情况下尚未设置的所有属性。这意味着他们需要同时设置 wordshighlightedWord。一个指定的初始化器不能将调用委托给另一个指定的初始化器,因此以下将不起作用:

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

并且所有便利初始化器都需要调用任何指定的初始化器,并且在调用指定的构造函数之前也不能直接设置或使用 self 属性。所以:

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

并且我希望从这个例子中可以清楚地看出便利初始化器可以有更多、更少或相同数量的输入参数。这一切都取决于。

一些更合理的例子:

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) }  
}

还有; required 关键字与此上下文中的任何内容无关。您可以将它放置到任何初始化器(指定或方便)、多个甚至所有初始化器中。

根据评论添加附加信息:

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.

这实际上取决于您的 class 的界面,您的示例中没有完全显示。例如,speed public是否暴露?

如果速度是 public,那么您正在寻找的是

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

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

甚至创建一个新的便捷构造函数

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)

但是,如果您的 speed 属性 是私有定义的,那么您只能在内部分配给它。您需要能够修改 class 以便它可以在某处接受此 属性。通过构造函数,通过 属性 或通过方法。在完全没有访问权限之前,您不能指望能够修改它。

但如果这是你自己的 class 如果可能的话,我会把事情颠倒过来。您的主要构造函数应该是接受所有 4 个参数的构造函数。但是应该需要您的便利初始值设定项(是的,可能需要便利初始值设定项)。所以在那种情况下你会:

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
    }
}

与您的代码相比,这个具体案例感觉它缺少了一些东西,但是同样,您没有提供 class 的完整定义,并且它确实或应该这样做。例如,在您提供的代码中,如果调用便利初始化程序,则以下行将按此顺序执行:

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

这看起来像是一个问题。

所以总结一下。有很多方法可以实现您的要求。但这完全取决于您要在此处构建的内容。

我希望它至少能让你走上正轨。