Swift 이니셜 라이저가 수퍼 클래스에서 편의 이니셜 라이저를 호출 할 수없는 이유는 무엇입니까?


88

두 가지 클래스를 고려하십시오.

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        super.init() // Error: Must call a designated initializer of the superclass 'A'
    }
}

왜 이것이 허용되지 않는지 모르겠습니다. 궁극적으로 각 클래스의 지정된 이니셜 라이저는 필요한 값으로 호출됩니다. 그래서 의 편의 가 잘 작동 할 때 기본값을 다시 지정하여 B's 에서 자신을 반복해야하는 이유는 무엇입니까?initxinitA


2
답을 찾았지만 만족할만한 답을 찾을 수 없습니다. 아마도 구현 이유 일 것입니다. 다른 클래스에서 지정된 이니셜 라이저를 검색하는 것이 편의 이니셜 라이저를 검색하는 것보다 훨씬 쉽습니다.
Sulthan

@Robert, 아래 의견에 감사드립니다. 나는 당신의 질문에 그들을 추가하거나 당신이받은 것에 대한 답변을 게시 할 수있을 것이라고 생각합니다 : "이것은 의도적으로 설계된 것이며 모든 관련 버그는이 영역에서 분류되었습니다." 따라서 이유를 설명 할 수 없거나 설명하고 싶지 않은 것 같습니다.
Ferran Maylinch

답변:


24

이것은 Swift Programming Guide에 명시된 "Initializer Chaining"규칙의 규칙 1입니다.

규칙 1 : 지정된 이니셜 라이저는 직계 수퍼 클래스에서 지정된 이니셜 라이저를 호출해야합니다 .

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html

내 강조. 지정된 이니셜 라이저는 편의 이니셜 라이저를 호출 할 수 없습니다.

이니셜 라이저 "방향"이 허용되는 규칙을 보여주는 다이어그램이 있습니다.

이니셜 라이저 체인


80
하지만 왜 이런 식으로 수행됩니까? 문서는 디자인을 단순화한다고 말하고 있지만 지정된 이니셜 라이저에 기본값을 계속 지정하여 계속 반복해야하는 경우 이것이 어떻게되는지는 알지 못합니다. 기본 설정이 편리 할 때 대부분 신경 쓰지 않습니다. 이니셜 라이저가할까요?
Robert

5
이 질문이 있은 지 며칠 후에 편리한 이니셜 라이저를 호출 할 수 있도록 버그 17266917을 제출했습니다. 아직 응답이 없지만 다른 Swift 버그 보고서에 대한보고도 없었습니다!
Robert

8
바라건대 그들은 편의 이니셜 라이저를 호출 할 수 있기를 바랍니다. SDK의 일부 클래스의 경우 특정 동작을 달성하는 다른 방법은 없지만 편의 이니셜 라이저를 호출합니다. 참조 SCNGeometry: SCNGeometryElement편리한 이니셜 라이저를 사용해서 만 s 를 추가 할 수 있으므로 상속 할 수 없습니다.
Spak

4
이것은 매우 잘못된 언어 디자인 결정입니다. 새 앱의 맨 처음부터 신속하게 개발하기로 결정했습니다. 하위 클래스에서 NSWindowController.init (windowNibName)을 호출해야하는데 그렇게 할 수 없습니다. (
Uniqus

4
@Kaiserludi : 유용한 것은 없습니다. 다음과 같은 응답으로 종료되었습니다.
Robert

21

중히 여기다

class A
{
    var a: Int
    var b: Int

    init (a: Int, b: Int) {
        print("Entering A.init(a,b)")
        self.a = a; self.b = b
    }

    convenience init(a: Int) {
        print("Entering A.init(a)")
        self.init(a: a, b: 0)
    }

    convenience init() {
        print("Entering A.init()")
        self.init(a:0)
    }
}


class B : A
{
    var c: Int

    override init(a: Int, b: Int)
    {
        print("Entering B.init(a,b)")
        self.c = 0; super.init(a: a, b: b)
    }
}

var b = B()

클래스 A의 모든 지정된 이니셜 라이저가 재정의되기 때문에 클래스 B는 A의 모든 편의 이니셜 라이저를 상속합니다. 따라서이를 실행하면

Entering A.init()
Entering A.init(a:)
Entering B.init(a:,b:)
Entering A.init(a:,b:)

이제 지정된 이니셜 라이저 B.init (a : b :)가 기본 클래스 편의 이니셜 라이저 A.init (a :)를 호출하도록 허용하면 B.init (a :, b :)에 대한 재귀 호출이 발생합니다. ).


해결하기 쉽습니다. 지정된 이니셜 라이저 내에서 수퍼 클래스의 편의 이니셜 라이저를 호출하자마자 수퍼 클래스의 편의 이니셜 라이저는 더 이상 상속되지 않습니다.
fluidsonic 2015

@fluidsonic이지만 구조 및 클래스 메서드가 사용 방법에 따라 변경되기 때문에 미쳤습니다. 디버깅의 재미를 상상해보십시오!
kdazzle

2
@kdazzle 클래스의 구조도 클래스 메서드도 변경되지 않습니다. 왜 그래야합니까? -내가 생각할 수있는 유일한 문제는 편의 이니셜 라이저가 상속 될 때 서브 클래스의 지정된 이니셜 라이저에 동적으로 위임하고 상속되지 않고 서브 클래스에서 위임 될 때 자체 클래스의 지정된 이니셜 라이저에 정적으로 위임해야한다는 것입니다.
fluidsonic

일반적인 방법으로도 비슷한 재귀 문제가 발생할 수 있다고 생각합니다. 메서드를 호출 할 때의 결정에 따라 다릅니다. 예를 들어 무한 루프에 들어갈 수 있기 때문에 언어가 재귀 호출을 허용하지 않는 경우 어리석은 일입니다. 프로그래머는 자신이하는 일을 이해해야합니다. :)
Ferran Maylinch

13

무한 재귀로 끝날 수 있기 때문입니다. 중히 여기다:

class SuperClass {
    init() {
    }

    convenience init(value: Int) {
        // calls init() of the current class
        // so init() for SubClass if the instance
        // is a SubClass
        self.init()
    }
}

class SubClass : SuperClass {
    override init() {
        super.init(value: 10)
    }
}

그리고보세요 :

let a = SubClass()

어느 전화 SubClass.init()가 전화 SuperClass.init(value:)할 것 SubClass.init()입니다.

지정 / 편의 초기화 규칙은 클래스 초기화가 항상 정확하도록 설계되었습니다.


1
서브 클래스가 수퍼 클래스에서 편의 이니셜 라이저를 명시 적으로 호출 할 수는 없지만, 서브 클래스가 모든 수퍼 클래스 지정된 이니셜 라이저 ( 예 :이 예제 ) 의 구현을 제공하므로 상속 할 수 있습니다 . 따라서 위의 예는 실제로 사실이지만 수퍼 클래스의 편의 이니셜 라이저와 명시 적으로 관련이있는 특별한 경우가 아니라 지정된 이니셜 라이저 가 편의를 호출하지 않을 수 있다는 사실 과 관련이 있습니다. 위와 같은 재귀 시나리오를 생성하기 때문입니다. .
dfrib

1

이에 대한 해결 방법을 찾았습니다. 매우 예쁘지는 않지만 수퍼 클래스의 값을 모르거나 기본값을 설정하려는 문제를 해결합니다.

당신이해야 할 일은 init바로 init서브 클래스 에서 편리함을 사용하여 슈퍼 클래스의 인스턴스를 만드는 것입니다 . 그런 다음 init방금 만든 인스턴스를 사용하여 지정된 수퍼를 호출합니다 .

class A {
    var x: Int

    init(x: Int) {
        self.x = x
    }

    convenience init() {
        self.init(x: 0)
    }
}

class B: A {
    init() {
        // calls A's convenience init, gets instance of A with default x value
        let intermediate = A() 

        super.init(x: intermediate.x) 
    }
}

1

편리한 초기화 코드를 init()새로운 도우미 함수로 추출하고 하위 클래스에서 초기화를 수행하도록 foo()호출 foo(...)하십시오.


좋은 제안이지만 실제로 질문에 대한 답은 아닙니다.
SwiftsNamesake

0

이니셜 라이저와 그 상속에 대한 자세한 설명은 18:30에 WWDC 비디오 "403 intermediate Swift"를 참조하십시오. 내가 이해했듯이 다음을 고려하십시오.

class Dragon {
    var legs: Int
    var isFlying: Bool

    init(legs: Int, isFlying: Bool) {
        self.legs = legs
        self.isFlying = isFlying
    }

    convenience initWyvern() { 
        self.init(legs: 2, isFlying: true)
    }
}

하지만 이제 Wyrm 하위 직업을 생각해보십시오. Wyrm은 다리도 날개도없는 용입니다. 따라서 와이번의 이니셜 라이저 (2 개 다리, 2 개 날개)가 잘못되었습니다! 편리한 Wyvern-Initializer를 단순히 호출 할 수없고 지정된 전체 초기화 프로그램 만 호출하면이 오류를 피할 수 있습니다.

class Wyrm: Dragon {
    init() {
        super.init(legs: 0, isFlying: false)
    }
}

12
그것은 정말로 이유가 아닙니다. initWyvern호출이 타당 할 때 서브 클래스를 만들면 어떻게됩니까?
Sulthan 2014-06-09

4
네, 확신이 없습니다. Wyrm편의 이니셜 라이저를 호출 한 후 레그 수를 재정의하는 것을 멈추지 않습니다 .
로버트

이것은 WWDC 비디오에 제공된 이유입니다 (자동차-> 경주 용 자동차 및 부울 hasTurbo 속성 만 해당). 서브 클래스가 지정된 이니셜 라이저를 모두 구현하면 편의 이니셜 라이저도 상속됩니다. 나는 그것의 의미를 보았습니다. 또한 솔직히 Objective-C가 작동하는 방식에 큰 문제가 없었습니다. Objective-C 에서처럼 처음이 아니라 init에서 마지막으로 super.init를 호출하는 새로운 규칙도 참조하십시오.
Ralf

IMO는 super.init를 "last"라고 부르는 규칙은 개념적으로 새로운 것이 아닙니다. object-c에서와 동일합니다. 단지 거기에서 모든 것이 자동으로 초기 값 (nil, 0, 무엇이든)이 할당되었습니다. Swift에서 우리는 초기화 단계를 직접 수행해야합니다. 이 접근 방식의 이점은 다른 초기 값을 할당 할 수도 있다는 것입니다.
Roshan

-1

왜 당신은 두 개의 이니셜 라이저를 가지고 있지 않습니까? 하나는 기본값을 가지고 있습니까?

class A {
  var x: Int

  init(x: Int) {
    self.x = x
  }

  init() {
    self.x = 0
  }
}

class B: A {
  override init() {
    super.init()

    // Do something else
  }
}

let s = B()
s.x // 0
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.