클래스가 수퍼 클래스의 필수 멤버를 구현하지 않습니다


155

그래서 오늘 Xcode 6 베타 5로 업데이트했으며 Apple 클래스의 거의 모든 하위 클래스에서 오류가 발생했습니다.

오류 상태 :

클래스 'x'는 수퍼 클래스의 필수 멤버를 구현하지 않습니다.

이 클래스는 현재 매우 가벼워 게시하기 쉽기 때문에 내가 선택한 예가 있습니다.

class InfoBar: SKSpriteNode  { //Error message here

    let team: Team
    let healthBar: SKSpriteNode

    init(team: Team, size: CGSize) {
        self.team = team
        if self.team == Team.TeamGood {
            healthBar = SKSpriteNode(color: UIColor.greenColor(), size:size)
        }
        else {
            healthBar = SKSpriteNode(color: UIColor.redColor(), size:size)
        }
        super.init(texture:nil, color: UIColor.darkGrayColor(), size: size)

        self.addChild(healthBar)

    }

}

그래서 내 질문은 왜이 오류가 발생하며 어떻게 해결할 수 있습니까? 내가 구현하지 않은 것은 무엇입니까? 지정된 초기화 프로그램을 호출하고 있습니다.

답변:


127

개발자 포럼의 Apple 직원 :

"NSCoding과 호환되고 싶지 않은 컴파일러 및 빌드 된 프로그램에 선언하는 방법은 다음과 같습니다."

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}

NSCoding을 준수하지 않으려는 경우 옵션입니다. 스토리 보드에서로드하지 않을 것이라는 것을 알고 있으므로 많은 SpriteKit 코드 로이 방법을 사용했습니다.


당신이 취할 수있는 또 다른 옵션은 다음과 같이 편리한 초기화로 메소드를 구현하는 것입니다.

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}

의 초기화 프로그램 호출에 유의하십시오 self. 이렇게하면 치명적 오류가 발생하지 않도록 모든 비 선택적 속성과 달리 매개 변수에 더미 값만 사용해야합니다.


세 번째 옵션은 super를 호출하는 동안 메소드를 구현하고 선택 사항이 아닌 모든 특성을 초기화하는 것입니다. 오브젝트가 스토리 보드에서로드되는 뷰인 경우이 방법을 사용해야합니다.

required init(coder aDecoder: NSCoder!) {
    foo = "some string"
    bar = 9001

    super.init(coder: aDecoder)
}

3
그러나 두 번째 옵션은 대부분의 실제 상황에서는 쓸모가 없습니다. 예를 들어 필요한 초기화 프로그램을 사용하십시오 init(collection:MPMediaItemCollection). 실제 미디어 항목 모음을 제공해야합니다. 그것이이 수업의 요점입니다. 이 클래스는 클래스없이 인스턴스화 할 수 없습니다. 컬렉션을 분석하고 12 개의 인스턴스 변수를 초기화합니다. 이것이 유일하고 지정된 이니셜 라이저가되는 요점입니다! 따라서 init(coder:)여기서 제공 할 의미가있는 (또는 의미없는) MPMediaItemCollection이 없습니다. fatalError접근 방식 만 옳습니다.
matt

@matt 맞음, 하나 또는 다른 옵션은 다른 상황에서 더 잘 작동합니다.
벤 케인

맞습니다. 저는 두 번째 옵션을 독립적으로 발견하고 고려했으며 때로는 의미가 있습니다. 예를 들어, 나는 내 di를 선언 할 수 있습니다 init(collection:MPMediaItemCollection!). 그것은 init(coder:)nil을 통과 시킬 수 있습니다. 그러나 나는 깨달았습니다 : 아니요, 이제 컴파일러를 속이고 있습니다. nil을 전달하는 것은 허용되지 않으므로을 던지고 fatalError계속 진행하십시오. :)
matt

1
나는이 질문과 그 대답이 오래되었다는 것을 알고 있지만 기존 답변 중 하나로 해결되지 않은이 오류를 실제로 이해하는 것이 중요하다고 생각하는 몇 가지 요점을 다루는 새로운 대답을 게시했습니다.
nhgrif

좋은 대답입니다. Swift가 항상 슈퍼 이니셜 라이저를 상속하지는 않는다는 것을 이해하는 것이이 패턴을 이해하는 데 필수적이라는 것에 동의합니다.
벤 케인

71

기존 답변에서 누락 된 Swift 관련 정보 에는 두 가지가 절대적으로 중요 합니다.

  1. 프로토콜이 이니셜 라이저를 필수 메소드로 지정하는 경우 해당 이니셜 라이저는 Swift의 required키워드를 사용하여 표시해야합니다 .
  2. 스위프트에는 init메소드 와 관련된 특별한 상속 규칙이 있습니다.

TL; DR은 이것이다 :

이니셜 라이저를 구현하면 더 이상 수퍼 클래스의 지정된 이니셜 라이저를 상속하지 않습니다.

상속받을 유일한 이니셜 라이저는 무시한 지정된 이니셜 라이저를 가리키는 수퍼 클래스 편의 이니셜 라이저입니다.

긴 버전을 준비 했습니까?


스위프트에는 init메소드 와 관련된 특별한 상속 규칙이 있습니다.

나는 이것이 두 가지 요점 중 두 번째라는 것을 알고 있지만 첫 번째 요점 required을 이해할 수 없거나이 요점을 이해할 때까지 키워드가 존재하는 이유를 알 수 없습니다 . 일단 우리가이 점을 이해하면, 다른 점은 꽤 분명해집니다.

이 답변의이 섹션에서 다루는 모든 정보는 여기에있는 Apple의 설명서를 참조하십시오 .

Apple 문서에서 :

Objective-C의 서브 클래스와 달리 Swift 서브 클래스는 기본적으로 수퍼 클래스 이니셜 라이저를 상속하지 않습니다. Swift의 접근 방식은 수퍼 클래스의 간단한 이니셜 라이저가보다 특수한 서브 클래스에 의해 상속되고 완전히 또는 올바르게 초기화되지 않은 서브 클래스의 새 인스턴스를 작성하는 데 사용되는 상황을 방지합니다.

강조합니다.

따라서 Apple 문서에서 바로 Swift 서브 클래스가 항상 슈퍼 클래스의 init메소드를 상속하지는 않습니다 .

그렇다면 언제 슈퍼 클래스에서 상속 받습니까?

서브 클래스 init가 부모 로부터 메소드를 상속하는시기를 정의하는 두 가지 규칙이 있습니다 . Apple 문서에서 :

규칙 1

서브 클래스가 지정된 이니셜 라이저를 정의하지 않으면 모든 수퍼 클래스 지정 이니셜 라이저를 자동으로 상속합니다.

규칙 2

서브 클래스가 규칙 1에 따라 상속 받거나 정의의 일부로 사용자 정의 구현을 제공하여 모든 수퍼 클래스 지정 이니셜 라이저의 구현을 제공하는 경우 모든 수퍼 클래스 편의 이니셜 라이저를 자동으로 상속합니다.

때문에 규칙이이 대화에 특히 관련이없는 SKSpriteNode의이 init(coder: NSCoder)편리한 메소드 수 없을 수도 있습니다.

따라서 InfoBar클래스는 required추가 할 때까지 바로 초기화 프로그램 을 상속 했습니다 init(team: Team, size: CGSize).

이 제공되지 않은 것 인 경우에 init방법을 대신하여 만든 InfoBar'옵션 추가 s의 속성 또는 기본 값을 제공, 당신은 여전히 상속 된 것 SKSpriteNode'이야 init(coder: NSCoder). 그러나 우리 자신의 사용자 정의 이니셜 라이저를 추가했을 때, 우리는 슈퍼 클래스의 지정된 이니셜 라이저 (및 구현 한 이니셜 라이저를 가리 키지 않는 편의 이니셜 라이저)의 상속을 중단했습니다 .

그래서 간단한 예로, 나는 이것을 제시한다.

class Foo {
    var foo: String
    init(foo: String) {
        self.foo = foo
    }
}

class Bar: Foo {
    var bar: String
    init(foo: String, bar: String) {
        self.bar = bar
        super.init(foo: foo)
    }
}


let x = Bar(foo: "Foo")

다음과 같은 오류가 발생합니다.

호출중인 'bar'매개 변수에 대한 인수가 누락되었습니다.

여기에 이미지 설명을 입력하십시오

이것이 Objective-C라면 상속에 아무런 문제가 없습니다. Objective-C에서 Barwith initWithFoo:를 초기화 하면 self.bar속성은 간단하게 nil됩니다. 아마 잘은 아니지만, 완벽의 유효한 객체가 될위한 상태. 그건 없습니다 . 스위프트의 객체가 될하기위한 완벽하게 유효한 상태 self.bar선택하지 않고 수 없습니다 nil.

다시 한번, 우리가 이니셜 라이저를 상속받는 유일한 방법은 우리 자신의 것을 제공하지 않는 것입니다. 우리는 삭제하여 상속하려고 그렇다면 Bar'들 init(foo: String, bar: String)과 같은 :

class Bar: Foo {
    var bar: String
}

이제 우리는 상속 (다양한)으로 돌아 왔지만 컴파일되지는 않습니다 ... 오류 메시지는 왜 슈퍼 클래스 init메소드를 상속하지 않는지를 정확하게 설명 합니다.

문제 : 클래스 'Bar'에 이니셜 라이저가 없습니다.

Fix-It : 이니셜 라이저가없는 저장된 속성 'bar'는 합성 된 이니셜 라이저를 방지합니다.

서브 클래스에 저장된 속성을 추가했다면 서브 클래스의 저장된 속성에 대해 알 수 없었던 수퍼 클래스 이니셜 라이저를 사용하여 서브 클래스의 유효한 인스턴스를 생성하는 스위프트 방법은 없습니다.


좋아, 왜 구현 init(coder: NSCoder)해야합니까? 왜 그렇 required습니까?

Swift의 init메소드는 특별한 상속 규칙 세트에 의해 작동 할 수 있지만 프로토콜 적합성은 여전히 ​​체인 아래로 상속됩니다. 부모 클래스가 프로토콜을 준수하는 경우 해당 서브 클래스가 해당 프로토콜을 준수해야합니다.

대부분의 프로토콜에는 Swift의 특수 상속 규칙에 따라 재생되지 않는 메소드 만 필요하므로 일반적으로 문제가되지 않으므로 프로토콜을 준수하는 클래스에서 상속하는 경우 모든 프로토콜을 상속합니다. 클래스가 프로토콜 적합성을 만족시킬 수있는 메소드 또는 프로퍼티

그러나 Swift의 init메소드는 특별한 규칙 세트를 사용하며 항상 상속되는 것은 아닙니다. 이 때문에 특수 init메소드 (예 :)가 필요한 프로토콜을 따르는 클래스는 NSCoding해당 init메소드를로 표시해야합니다 required.

이 예제를 고려하십시오.

protocol InitProtocol {
    init(foo: Int)
}

class ConformingClass: InitProtocol {
    var foo: Int
    init(foo: Int) {
        self.foo = foo
    }
}

컴파일되지 않습니다. 다음과 같은 경고가 생성됩니다.

문제 : 이니셜 라이저 요구 사항 'init (foo :)'는 최종 클래스가 아닌 'ConformingClass'의 '필수'이니셜 라이저 만 충족 할 수 있습니다.

Fix-It : 인서트 필요

init(foo: Int)이니셜 라이저를 필수 로 만들고 싶습니다 . 클래스를 만들어서 행복하게 만들 수도 있습니다 final(클래스를 상속받을 수 없음을 의미).

하위 클래스를 만들면 어떻게됩니까? 이 시점에서 하위 클래스를 작성하면 괜찮습니다. 그래도 초기화 프로그램을 추가하면 갑자기 더 이상 상속되지 않습니다 init(foo:). 이제 더 이상에 따르지 않기 때문에 문제가 InitProtocol됩니다. 프로토콜을 따르는 클래스에서 서브 클래스를 만들 수 없으며 갑자기 해당 프로토콜을 따르지 않기로 결정합니다. 프로토콜 준수를 상속했지만 Swift가 init메소드 상속 과 작동하는 방식으로 인해 해당 프로토콜을 준수하는 데 필요한 일부를 상속하지 않았으므로 구현해야합니다.


좋아,이 모든 것이 말이된다. 그러나 왜 더 유용한 오류 메시지를 얻을 수 없습니까?

클래스가 더 이상 상속 된 NSCoding프로토콜을 따르지 않고 이를 수정해야 한다고 지정한 경우 오류 메시지가 더 명확하거나 더 좋을 수 있습니다 init(coder: NSCoder). 확실한.

그러나 Xcode는 단순히 메시지를 생성 할 수 없습니다. 실제로 필요한 메소드를 구현하거나 상속하지 않는 데 실제로 실제 문제가되는 것은 아닙니다. 프로토콜 준수 외에 init메소드 를 작성 required해야하는 다른 이유가 적어도 하나 있습니다. 이것이 팩토리 메소드입니다.

올바른 팩토리 메소드를 작성하려면 리턴 유형을 Self(Swift의 Objective-C와 동일) 로 지정해야합니다 instanceType. 그러나 이렇게하려면 실제로 required초기화 메소드 를 사용해야합니다 .

class Box {
    var size: CGSize
    init(size: CGSize) {
        self.size = size
    }

    class func factory() -> Self {
        return self.init(size: CGSizeZero)
    }
}

오류가 발생합니다.

메타 타입 값으로 'Self'클래스 유형의 오브젝트를 구성하려면 '필수'이니셜 라이저를 사용해야합니다

여기에 이미지 설명을 입력하십시오

기본적으로 같은 문제입니다. 하위 클래스 Box인 경우 하위 클래스는 클래스 메서드를 상속합니다 factory. 그래서 우리는 전화 할 수있었습니다 SubclassedBox.factory(). 그러나없이 required키워드 on init(size:)방법 Box의 서브 클래스는 상속 보장 할 수 없습니다 self.init(size:)factory호출됩니다.

그래서 우리는 required이와 같은 팩토리 메소드를 원한다면 그 메소드를 만들어야합니다. 즉 , 클래스가 이와 같은 메소드를 구현하면 required초기화 메소드가 있고 여기에서 실행했던 것과 동일한 문제가 발생합니다. 와 NSCoding프로토콜입니다.


궁극적으로 Swift의 이니셜 라이저가 약간 다른 상속 규칙 세트를 사용한다는 기본 이해로 요약됩니다. 즉, 슈퍼 클래스에서 이니셜 라이저를 상속받지 못할 수도 있습니다. 슈퍼 클래스 이니셜 라이저는 새로운 저장된 속성을 알 수없고 객체를 유효한 상태로 인스턴스화 할 수 없기 때문에 발생합니다. 그러나 여러 가지 이유로 수퍼 클래스는 이니셜 라이저를로 표시 할 수 있습니다 required. 그렇게 할 때 실제로 required메서드를 상속하는 매우 구체적인 시나리오 중 하나를 사용 하거나 직접 구현해야합니다.

여기서 중요한 점은 여기에 오류가 발생하면 클래스가 실제로 메소드를 구현하지 않는다는 것을 의미합니다.

Swift 서브 클래스가 항상 부모의 init메소드를 상속하지는 않는다는 사실을 익히기위한 마지막 예제 일 수 있습니다 (이 문제를 완전히 이해하는 데 절대적으로 중요하다고 생각합니다).

class Foo {
    init(a: Int, b: Int, c: Int) {
        // do nothing
    }
}

class Bar: Foo {
    init(string: String) {
        super.init(a: 0, b: 1, c: 2)
        // do more nothing
    }
}

let f = Foo(a: 0, b: 1, c: 2)
let b = Bar(a: 0, b: 1, c: 2)

컴파일에 실패했습니다.

여기에 이미지 설명을 입력하십시오

오류 메시지는 약간 오해의 소지가 있습니다.

추가 인수 'b'호출

그러나 요점은 부모 클래스에서 메소드 를 상속하는 두 가지 특별한 경우 중 하나를 만족하지 않기 때문에 의 메소드를 Bar상속하지 않는다는 것 입니다.Fooinitinit

이것이 Objective-C라면 initObjective-C는 객체의 속성을 초기화하지 않는 것이 기쁘기 때문에 문제없이 상속받습니다 . Swift에서는 간단하지 않습니다. 유효하지 않은 상태를 가질 수 없으며 상속되는 수퍼 클래스 이니셜 라이저는 유효하지 않은 오브젝트 상태 만 초래할 수 있습니다.


이 문장의 의미를 설명하거나 예를 들어 주시겠습니까? "(그리고 우리가 구현 한 이니셜 라이저를 가리 키지 않은 편의 이니셜 라이저)"
Abbey Jackson

훌륭한 답변! 더 많은 SO 게시물 이이 방법 과 같은 이유가 아닌 왜 그런지 에 대해 바랍니다 .
Alexander Vasenin

56

이 문제가 발생한 이유는 무엇입니까? 글쎄, 분명한 사실은 클래스가 처리 할 준비가되지 않은 초기화 프로그램을 처리하는 것이 항상 중요하다는 것입니다 (예 : Mac OS X 10.0에서 Cocoa를 프로그래밍하기 시작한 이래 Objective-C에서). 문서는 이와 관련하여 귀하의 책임에 대해 항상 명확했습니다. 그러나 우리 중 얼마나 많은 사람들이 그것들을 온전하고 편지로 성취하기 위해 귀찮게 했습니까? 아마 우리 중 아무도! 그리고 컴파일러는 그것들을 강제하지 않았습니다. 그것은 모두 순전히 전통적이었습니다.

예를 들어, 지정된 이니셜 라이저가있는 Objective-C보기 컨트롤러 서브 클래스에서 :

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;

... 실제 미디어 항목 컬렉션을 전달하는 것이 중요합니다. 인스턴스가 없으면 인스턴스가 존재할 수 없습니다. 그러나 나는 누군가가 init대신 뼈로 나를 초기화하는 것을 막기 위해 "스토퍼"를 쓰지 않았습니다 . 내가 해야 하나 (실제로, 제대로 말하자면, 내가 쓴해야 구현 작성한 initWithNibName:bundle:, 상속 지정된 초기화를); 그러나 나는 "알기"때문에 자신의 클래스를 잘못 초기화하지 않기 때문에 너무 게으르다. 이것은 벌어진 구멍을 남겼습니다. Objective-C에서 누군가 베어 본을 호출 init하여 내 ivar을 초기화하지 않은 상태로 둘 수 있으며 패들 없이도 개울에 올라 있습니다.

Swift는 놀랍게도 대부분의 경우 저 자신을 구해줍니다. 이 응용 프로그램을 Swift로 번역하자마자 모든 문제가 해결되었습니다. 스위프트가 효과적으로 스토퍼를 만듭니다! init(collection:MPMediaItemCollection)클래스에서 지정된 이니셜 라이저가 선언 된 경우 bare-bones를 호출하여 초기화 할 수 없습니다 init(). 기적입니다!

시드 5에서 일어난 일은 init(coder:)이론적 으로이 클래스의 인스턴스가 펜촉에서 나올 수 있으며 컴파일러가 그것을 막을 수 없기 때문에 컴파일러가 기적이 작동하지 않는다는 것을 깨달았습니다 . 펜촉 하중 init(coder:)이 호출됩니다. 따라서 컴파일러는 스토퍼를 명시 적으로 작성합니다. 그리고 아주 그렇습니다.


자세한 답변을 주셔서 감사합니다. 이것은 실제로 문제에 빛을 가져옵니다.
Julian Osorio

컴파일러를 종료시키는 방법을 알려주는 pasta12에 대한 찬성이지만, 처음에 무엇이 나왔는지에 대해 알려주는 것에 대한 찬성.
개렛 올브라이트

2
구멍을 뚫고 있든 없든, 나는이 init을 결코 호출하지 않을 것이기 때문에 그것을 포함하도록 강요하는 것은 전적으로 충실합니다. bloated 코드는 우리 모두가 필요로하지 않는 오버 헤드입니다. 또한 이제 두 속성 모두에서 속성을 초기화해야합니다. 무의미한!
Dan Greenfield

5
@ DanGreenfield 아니요, 호출하지 않을 경우 stackoverflow.com/a/25128815/341994에fatalError 설명 된 스토퍼에 넣기 때문에 아무것도 초기화하지 않습니다 . 사용자 코드 스 니펫으로 만드십시오. 이제부터 필요한 곳에 배치 할 수 있습니다. 0.5 초가 걸립니다.
matt

1
@nhgrif 글쎄요, 공정하게 말하면, 그 질문은 전체적인 이야기를 요구하지 않았습니다. 이 잼에서 벗어나 계속 진행하는 방법에 관한 것입니다. 전체 내용은 내 책에 나와 있습니다 : apeth.com/swiftBook/ch04.html#_class_initializers
matt

33

더하다

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}

3
이것은 효과가 있지만 버그라고 생각하지 않습니다. 이니셜 라이저는 신속하게 상속되지 않으며 (자신의 이니셜 라이저가 선언 된 경우) 필수 키워드로 표시됩니다. 유일한 문제는 이제이 클래스를 사용하지 않을 때 낭비되는 코드가 많은 클래스 각각에 대해이 메서드에서 모든 속성을 초기화해야한다는 것입니다. 또는 원하지 않는 초기화를 무시하기 위해 모든 속성을 암시 적으로 래핑되지 않은 선택적 유형으로 선언해야합니다.
Epic Byte

1
예! 나는 그것이 버그일지도 모른다고 말하자마자 그것이 논리적으로 이해가된다는 것을 깨달았다. 나는 당신과 같이이 init 메소드를 절대 사용하지 않기 때문에 많은 낭비되는 코드가 될 것이라는 데 동의합니다. 아직 우아한 솔루션에 대해 잘 모르겠습니다
Gagan Singh

2
나는 같은 문제가 있었다. "필수 init"로 이해가되지만, swift는 내가 원했던 "쉬운"언어가 아닙니다. 이러한 모든 "선택적"은 언어를 필요 이상으로 복잡하게 만듭니다. DSL 및 AOP는 지원하지 않습니다. 점점 더 실망하고 있습니다.
user810395

2
예, 전적으로 동의합니다. 실제로 속성을 허용하지 않아야 할 때 강제로 수행해야하기 때문에 많은 속성이 옵션으로 선언되었습니다. 합법적으로 선택 사항이어야하기 때문에 선택 사항입니다 (없음은 유효한 값임). 그런 다음 하위 클래스가 아닌 클래스에서는 옵션을 사용할 필요가 없으므로 매우 복잡해져 올바른 코딩 스타일을 찾지 못하는 것 같습니다. 잘만되면 애플은 무언가를 알아 낸다.
Epic Byte

5
나는 그들이 자신의 이니셜 라이저를 선언하지 않으면 필요한 이니셜 라이저를 만족시킬 수 있으며 모든 이니셜 라이저가 상속받을 수 있다고 생각합니다.
Epic Byte
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.