【问题标题】:Does Convenience Init need to have same number of parameters as Required Init便利初始化是否需要与必需初始化具有相同数量的参数
【发布时间】:2022-01-03 07:33:52
【问题描述】:

发件人:https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

每当一个公共的快捷方式创建便利初始化程序 初始化模式将节省时间或进行初始化 类的意图更清晰。

我对便捷初始化的理解基本上是作为“典型”输入参数的快捷方式。大多数时候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 参数?

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

【问题讨论】:

  • 您的问题是什么?您已经得出了正确的结论,即便利 init 可以具有与所需 init 不同的参数。
  • 我希望能够访问不在convenience init 方法中的速度参数。但我似乎无法访问它,或者我不知道如何访问。如果我将 pod 的便捷方法更改为内部也包含速度参数,我将能够将值传递给速度参数。 (本质上使便利初始化具有与所需初始化相同的参数#)
  • 您可以从属于类/结构的任何 init 中访问任何内容。你试过了吗?
  • 我尝试了各种方法(我知道),但唯一对我有用的方法是将 speed 参数添加到便利初始化中。例如:我尝试了let p = RecordMessage(); p.leftRightBalance = 44(这导致错误提示cannot assign property: leftRightBalance setter is inaccessible)
  • 那么这样做是有原因的,要么您根本无法设置该属性,要么存在用于更新它的特定功能。

标签: ios swift initialization


【解决方案1】:

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

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

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

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

在这里,我创建了一个带有 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 关键字与此上下文中的任何内容无关。您可以将它放置到任何初始化器(指定或方便)、多个甚至所有初始化器中。

根据评论添加额外信息:

你的例子表明你可以有一个方便的 init 可以通过 参数返回到所需的 init``。我的情况怎么样 没有方便的 init``` 可以将值传递给参数 一个在必需的 init 中,但不在便利的 init 中。喜欢我的 我原来的问题中的速度参数。

这实际上取决于您的示例中未完全显示的类的接口。比如speed是否公开?

如果速度是公开的,那么您正在寻找的是类似的东西

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 属性是私有定义的,那么您只能在内部分配给它。您需要能够修改您的类,以便它可以在某处接受此属性。通过构造函数、属性或方法。在完全没有访问权限之前,您不能指望能够修改它。

但如果这是你自己的班级,我会尽可能把事情颠倒过来。您的主要构造函数应该是接受所有 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
    }
}

与您的代码相比,此特定案例感觉像是缺少某些内容,但同样,您没有提供类的完整定义,并且它确实或应该这样做。例如,在您提供的代码中,如果有人会调用便利初始化程序,那么以下行将按此顺序执行:

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

这似乎是个问题。

所以总结一下。有很多方法可以实现您的要求。但这一切都取决于您要在这里构建什么。

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

【讨论】:

  • 回到这个..你所有的例子都表明你可以有一个convenience init,它可以将参数传递回required init``. what about instances where I there is no convenience init```,它可以将一个值传递给一个参数它在required init 中,但不在convenience init 中。就像我原来问题中的 speed 参数一样。
  • @app4g 请尝试编辑后的答案。我在底部添加了一个新部分。
  • 这是很好的解释。真心赞赏。我再次查看了该类(它是来自 github.com/FitnessKit/FitDataProtocol/blob/master/Sources/… 的 pod)并且 speed 参数被定义为 private var 现在我明白了为什么我无法通过 itemInstance.speed = speed 访问它。我还与 pod 开发人员确认,从 convenience init 中省略了这些参数是一个疏忽,现在加上你的回答 - 我明白为什么会这样。
猜你喜欢
  • 1970-01-01
  • 2017-11-10
  • 2023-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多