development

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

big-blog 2020. 6. 7. 00:46
반응형

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


그래서 오늘 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)

    }

}

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


개발자 포럼의 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)
}

기존 답변에서 누락 된 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
    }
}

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

Issue: Initializer requirement 'init(foo:)' can only be satisfied by a 'required' initializer in non-final class 'ConformingClass'

Fix-It: Insert required

It wants me to make the init(foo: Int) initializer required. I could also make it happy by making the class final (meaning the class can't be inherited from).

So, what happens if I subclass? From this point, if I subclass, I'm fine. If I add any initializers though, I am suddenly no longer inheriting init(foo:). This is problematic because now I'm no longer conforming to the InitProtocol. I can't subclass from a class that conforms to a protocol and then suddenly decide I no longer want to conform to that protocol. I've inherited protocol conformance, but because of the way Swift works with init method inheritance, I've not inherited part of what's required to conform to that protocol and I must implement it.


Okay, this all makes sense. But why can't I get a more helpful error message?

Arguably, the error message might be more clear or better if it specified that your class was no longer conforming to the inherited NSCoding protocol and that to fix it you need to implement init(coder: NSCoder). Sure.

But Xcode simply can't generate that message because that actually won't always be the actual problem with not implementing or inheriting a required method. There is at least one other reason to make init methods required besides protocol conformance, and that's factory methods.

If I want to write a proper factory method, I need to specify the return type to be Self (Swift's equivalent of Objective-C's instanceType). But in order to do this, I actually need to use a required initializer method.

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

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

This generates the error:

Constructing an object of class type 'Self' with a metatype value must use a 'required' initializer

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

It's basically the same problem. If we subclass Box, our subclasses will inherit the class method factory. So we could call SubclassedBox.factory(). However, without the required keyword on the init(size:) method, Box's subclasses are not guaranteed to inherit the self.init(size:) that factory is calling.

So we must make that method required if we want a factory method like this, and that means if our class implements a method like this, we'll have a required initializer method and we'll run into the exact same problems you've run into here with the NSCoding protocol.


Ultimately, it all boils down to the basic understanding that Swift's initializers play by a slightly different set of inheritance rules which means you're not guaranteed to inherit initializers from your superclass. This happens because superclass initializers can't know about your new stored properties and they couldn't instantiate your object into a valid state. But, for various reasons, a superclass might mark an initializer as required. When it does, we can either employ one of the very specific scenarios by which we actually do inherit the required method, or we must implement it ourselves.

The main point here though is that if we're getting the error you see here, it means that your class isn't actually implementing the method at all.

As perhaps one final example to drill in the fact that Swift subclasses don't always inherit their parent's init methods (which I think is absolutely central to fully understanding this problem), consider this example:

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)

This fails to compile.

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

The error message it gives is a little misleading:

Extra argument 'b' in call

But the point is, Bar doesn't inherit any of Foo's init methods because it hasn't satisfied either of the two special cases for inheriting init methods from its parent class.

If this were Objective-C, we'd inherit that init with no problem, because Objective-C is perfectly happy not initializing objects' properties (though as a developer, you shouldn't have been happy with this). In Swift, this simply will not do. You can't have an invalid state, and inheriting superclass initializers can only lead to invalid object states.


Why has this issue arisen? Well, the plain fact is that it has always been important (i.e. in Objective-C, since the day I started programming Cocoa back in Mac OS X 10.0) to deal with initializers that your class is not prepared to handle. The docs have always been quite clear about your responsibilities in this regard. But how many of us bothered to fulfill them, completely and to the letter? Probably none of us! And the compiler did not enforce them; it was all purely conventional.

For example, in my Objective-C view controller subclass with this designated initializer:

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

...it is crucial that we be passed an actual media item collection: the instance simply cannot come into existence without one. But I have written no "stopper" to prevent someone from initializing me with bare-bones init instead. I should have written one (actually, properly speaking, I should have written an implementation of initWithNibName:bundle:, the inherited designated initializer); but I was too lazy to bother, because I "knew" I would never incorrectly initialize my own class that way. This left a gaping hole. In Objective-C, someone can call bare-bones init, leaving my ivars uninitialized, and we are up the creek without a paddle.

Swift, wonderfully, saves me from myself in most cases. As soon as I translated this app into Swift, the whole problem went away. Swift effectively creates a stopper for me! If init(collection:MPMediaItemCollection) is the only designated initializer declared in my class, I can't be initialized by calling bare-bones init(). It's a miracle!

What's happened in seed 5 is merely that the compiler has realized that the miracle doesn't work in the case of init(coder:), because in theory an instance of this class could come from a nib, and the compiler can't prevent that — and when the nib loads, init(coder:) will be called. So the compiler makes you write the stopper explicitly. And quite right too.


add

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

참고URL : https://stackoverflow.com/questions/25126295/class-does-not-implement-its-superclasss-required-members

반응형