web-dev-qa-db-ja.com

定数プロパティは、派生クラスの便利な初期化子では初期化できません

以下は、遊び場からのサンプルコードです。私が理解していないのは、サブクラスのb変数がvar型でなければならず、letにすることができない理由です。誰かが私を理解するのを手伝ってもらえますか?

class Base1 {
    init() { }
}

class Sub1: Base1 {
    let b: Int

    override init() {
        super.init()
    }

    convenience init(b: Int) {
        self.b = b  // Cannot assign to property: 'b' is a 'let' constant
        self.init()
    }
}
12
Aaron Bratcher

これはサブクラス化とは関係がなく、クラス初期化子と関係があると思います。

エラーは話の半分しか伝えていません。bletからvarに変更すると、このクラスで他の問題が発生します。

  • 初期化子は、クラスの格納されたプロパティごとに初期値を設定する必要があります。 initをオーバーライドする場合は、bのデフォルト値を指定する必要があります。

  • コンビニエンスイニシャライザは、selfにアクセスする前に、指定されたイニシャライザを呼び出す必要があります。

これがその外観です(blet定数として維持する方法に関するコメントで提案された改善についてMartinRに感謝します):

class Base1 {
    init() { }
}

class Sub1: Base1 {
    let b: Int

    convenience override init() {
        self.init(b: 5)
    }

    init(b: Int) {
        self.b = b
        super.init()
    }
}

let one = Sub1(b: 10)
one.b   // prints 10
let two = Sub1()
two.b   // prints 5
6
paulvs

まず第一に、私見ですが、これには派生クラスには何もありません。サブクラスを削除して、同様のエラーが発生する可能性があります。

第二に、それは便利な初期化子の「性質」とその使用法によるものだと思います。これらは「オプション」(使用法)の初期化子であり、たとえば、いくつかの複雑なデータ(他のクラスや構造など)から初期化するためのショートカット方法を提供します。 self.bの初期化をサンプルの便利なinitに追加しても、とにかく指定されたinitでbを初期化する必要があります

class Sub1: Base1 {
    let b: Int

    // You must init b
    // Or ger error: property 'self.b' not initialized ...
   init(b: Int) {
        self.b = b
        super.init()
    }

    // Do you really need the code below now?
    //convenience init(b: Int) {
    //    self.b = b
    //    self.init()
    //}
}

したがって、指定されたinit must initialize bとして、あなたが書いた便利な初期化子は無意味になります。

第三に、許可されると仮定します。したがって、便利な初期化子は、必ずしも指定されたものを呼び出す必要はなく、委任されます。したがって、次のようなことを行うことができます。

...

convenience init(b: Int) {
    self.b = b
    self.init(c: 10)
}

// later some crazy guy adds his own "convenience" initializer and points your to this one.
convenience init(c: Int) {
    self.c = c
    if c == 7 {
       self.b = 11
    }
    self.init()
}

ここで、あなたが「コンパイラ」であると想像して、constant bconstant valueおよびwhatに設定されている場所を教えてください値?

最後に、私の理解では、正しい使用法は次のようになります。

class Base1 {
    init() {}
}


class Sub1: Base1 {
    let b: Int

    init(b: Int) {
        self.b = b
        super.init()
    }

    // Convenience initializer providing default value
    override convenience init() {
        self.init(b: 7)
    }
}

だから、私の懸念は、便利なinitでlet bを初期化できるようにすることで、あなたが本当に達成したいことを明確に理解していないということです。

2

Swiftでは、指定された初期化子を使用して、保存されているすべてのプロパティを設定します。これは公式からのものです ドキュメント

指定初期化子は、クラスの主要な初期化子です。指定された初期化子は、そのクラスによって導入されたすべてのプロパティを完全に初期化し、適切なスーパークラス初期化子を呼び出して、スーパークラスチェーンの上流で初期化プロセスを続行します。

コンビニエンスイニシャライザはセカンダリであり、指定されたイニシャライザを呼び出す必要があります。

便利な初期化子を定義して、特定のユースケースまたは入力値タイプ用にそのクラスのインスタンスを作成することもできます。一般的な初期化パターンへのショートカットが時間を節約したり、クラスの初期化を意図的に明確にする場合はいつでも、便利な初期化子を作成します。

コードを試しているとき、この時点ではエラーメッセージは明確ではありませんが、問題は、便利なinitを使用して保存されたプロパティを初期化できないことです。それを機能させるには、bをオプションのvarにする必要があります。

class Sub1: Base1 {
    var b: Int?

    override init() {
        super.init()
    }

    convenience init(b: Int) {
        self.init()
        self.b = b
    }
}

または、@ Martin Rがコメントの1つで提案したように、別の指定された初期化子を作成し、initをオーバーライドする前に便利な方法を使用して、デフォルト値を指定することをお勧めします。

class Sub1: Base1 {
    let b: Int

    init(b: Int) {
        self.b = b
        super.init()
    }

    convenience override init() {
        self.init(b:5)
    }
}
1
Puneet Sharma