类不实现其超类的必需成员

Epi*_*yte 155 ios sprite-kit swift

所以我今天更新到Xcode 6 beta 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)

    }

}
Run Code Online (Sandbox Code Playgroud)

所以我的问题是,为什么我收到此错误,我该如何解决?我没有实施什么?我正在打电话给指定的初始化程序.

Ben*_*ane 127

来自开发者论坛上的Apple员工:

"向编译器和构建的程序声明你真的不希望与NSCoding兼容的一种方法是做这样的事情:"

required init(coder: NSCoder) {
  fatalError("NSCoding not supported")
}
Run Code Online (Sandbox Code Playgroud)

如果您知道您不希望符合NSCoding,则可以选择此选项.我已经使用了很多我的SpriteKit代码,因为我知道我不会从故事板中加载它.


您可以采用的另一个选项是将方法实现为便利init,如下所示:

convenience required init(coder: NSCoder) {
    self.init(stringParam: "", intParam: 5)
}
Run Code Online (Sandbox Code Playgroud)

请注意调用初始化程序self.这允许您只需要为参数使用虚拟值,而不是所有非可选属性,同时避免抛出致命错误.


第三种选择当然是在调用super时实现该方法,并初始化所有非可选属性.如果对象是从故事板加载的视图,则应采用此方法:

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

    super.init(coder: aDecoder)
}
Run Code Online (Sandbox Code Playgroud)

  • 但是,在大多数现实生活中,第二种选择都是无用的.举个例子,我需要的初始化程序`init(collection:MPMediaItemCollection)`.您必须提供真实的媒体项目集合; 这就是本课程的重点.没有一个这个类根本无法实例化.它将分析集合并初始化十几个实例变量.这就是唯一的指定初始化程序!因此,`init(coder:)`在这里没有有意义的(甚至无意义的)MPMediaItemCollection; 只有`fatalError`方法是正确的. (3认同)

nhg*_*rif 71

现有答案中缺少两个绝对关键的特定于Swift的信息,我认为这有助于彻底清除这些信息.

  1. 如果协议将初始化程序指定为必需方法,则必须使用Swift的required关键字标记该初始化程序.
  2. Swift有一套关于init方法的特殊继承规则.

文艺青年最爱的是这样的:

如果实现任何初始值设定项,则不再继承任何超类的指定初始值设定项.

您将继承的唯一初始值设定项(如果有)是超类便捷初始值设定项,它指向您碰巧覆盖的指定初始值设定项.

那么...准备好长版本?


Swift有一套关于init方法的特殊继承规则.

我知道这是我提出的两点中的第二点,但我们无法理解第一点,或者为什么required关键字甚至存在,直到我们理解这一点.一旦我们理解了这一点,另一个变得非常明显.

All of the information I cover in this section of this answer is from Apple's documentation found here.

From the Apple docs:

Unlike subclasses in Objective-C, Swift subclasses do not inherit their superclass initializers by default. Swift’s approach prevents a situation in which a simple initializer from a superclass is inherited by a more specialized subclass and is used to create a new instance of the subclass that is not fully or correctly initialized.

Emphasis mine.

So, straight from the Apple docs right there, we see that Swift subclasses will not always (and usually don't) inherit their superclass's init methods.

So, when do they inherit from their superclass?

There are two rules that define when a subclass inherits init methods from its parent. From the Apple docs:

Rule 1

If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.

Rule 2

If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

Rule 2 isn't particularly relevant to this conversation because SKSpriteNode's init(coder: NSCoder) is unlikely to be a convenience method.

So, your InfoBar class was inheriting the required initializer right up until the point that you added init(team: Team, size: CGSize).

If you were to have not provided this init method and instead made your InfoBar's added properties optional or provided them with default values, then you'd have still been inheriting SKSpriteNode's init(coder: NSCoder). However, when we added our own custom initializer, we stopped inheriting our superclass's designated initializers (and convenience initializers which didn't point to initializers we implemented).

So, as a simplistic example, I present this:

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")
Run Code Online (Sandbox Code Playgroud)

Which presents the following error:

Missing argument for parameter 'bar' in call.

在此输入图像描述

If this were Objective-C, it'd have no problem inheriting. If we initialized a Bar with initWithFoo: in Objective-C, the self.bar property would simply be nil. It's probably not great, but it's a perfectly valid state for the object to be in. It's not a perfectly valid state for the Swift object to be in. self.bar is not an optional and cannot be nil.

Again, the only way we inherit initializers is by not providing our own. So if we try to inherit by deleting Bar's init(foo: String, bar: String), as such:

class Bar: Foo {
    var bar: String
}
Run Code Online (Sandbox Code Playgroud)

Now we're back to inheriting (sort of), but this won't compile... and the error message explains exactly why we don't inherit superclass init methods:

Issue: Class 'Bar' has no initializers

Fix-It: Stored property 'bar' without initializers prevents synthesized initializers

If we've added stored properties in our subclass, there's no possible Swift way to create a valid instance of our subclass with the superclass initializers which couldn't possibly know about our subclass's stored properties.


Okay, well, why do I have to implement init(coder: NSCoder) at all? Why is it required?

Swift's init methods may play by a special set of inheritance rules, but protocol conformance is still inherited down the chain. If a parent class conforms to a protocol, its subclasses must to conform to that protocol.

Ordinarily, this isn't a problem, because most protocols only require methods which don't play by special inheritance rules in Swift, so if you're inheriting from a class that conforms to a protocol, you're also inheriting all of the methods or properties that allow the class to satisfy protocol conformance.

However, remember, Swift's init methods play by a special set of rules and aren't always inherited. Because of this, a class that conforms to a protocol which requires special init methods (such as NSCoding) requires that the class mark those init methods as required.

Consider this example:

protocol InitProtocol {
    init(foo: Int)
}

class ConformingClass: InitProtocol {
    var foo: Int
    init(foo: Int) {
        self.foo = foo
    }
}
Run Code Online (Sandbox Code Playgroud)

This doesn't compile. It generates the following warning:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

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.


mat*_*att 56

为什么会出现这个问题?嗯,显而易见的事实是它一直很重要(即在Objective-C中,因为我在Mac OS X 10.0中开始编程Cocoa的那天)来处理你的类不准备处理的初始化器.在这方面,文档一直非常清楚你的责任.但是,我们中有多少人为了履行它们而烦恼呢?可能我们都不是!并且编译器没有强制执行它们; 这一切都纯粹是传统的.

例如,在我的Objective-C视图控制器子类中使用此指定的初始化程序:

- (instancetype) initWithCollection: (MPMediaItemCollection*) coll;
Run Code Online (Sandbox Code Playgroud)

......我们传递一个真正的媒体项目集合是至关重要的:如果没有一个实例,实例就不可能存在.但我没有写任何"塞子"来防止有人用裸骨init来初始化我.我应该写一个(实际上,正确地说,我应该编写一个initWithNibName:bundle:继承的指定初始化器的实现); 但我太懒了,因为我"知道"我永远不会错误地初始化我自己的课程.这留下了一个巨大的漏洞.在Objective-C中,有人可以打电话给裸骨init,让我的ivars没有初始化,我们在没有桨的情况下上了小溪.

在大多数情况下,斯威夫特奇妙地将我从自己身上拯救出来.一旦我将这个应用程序翻译成Swift,整个问题就消失了.斯威夫特有效地为我制造了一个塞子!如果init(collection:MPMediaItemCollection)是我的类中声明的唯一指定的初始值设定项,我无法通过调用bare-bones来初始化init().这是一个奇迹!

在种子5中发生的事情仅仅是编译器已经意识到奇迹在这种情况下不起作用init(coder:),因为理论上这个类的实例可能来自一个nib,并且编译器无法阻止它 - 并且当nib加载,init(coder:)将被调用.所以编译器会让你明确地写下塞子.也很正确.

  • @DanGreenfield不,它不会强迫你初始化任何东西,因为如果你永远不打算调用它,你只需要输入http://stackoverflow.com/a/25128815/341994中描述的`fatalError`限制器.只需将其设置为用户代码片段,从现在开始,您可以将其放在需要的位置.需要半秒钟. (5认同)
  • 不管是不是开口,我永远不会打电话给这个初学者,所以迫使我把它包括起来是完全苛刻的.膨胀的代码是我们都不需要的开销.它现在也迫使你在两个inits中初始化你的属性.无意义! (2认同)

Gag*_*ngh 33

required init(coder aDecoder: NSCoder!) {
  super.init(coder: aDecoder)
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为它们意味着您可以通过不声明自己的任何初始化程序来满足所需的初始化程序,这将导致所有初始化程序被继承. (5认同)
  • 这确实有效,但我不认为这是一个错误.初始化程序不会在swift中继承(当您声明自己的初始化程序时)并且使用required关键字标记.唯一的问题是,现在我需要在这个方法中为我的每个类初始化我的所有属性,这将是很多浪费的代码,因为我根本不使用它.或者我将必须声明我的所有属性作为隐式解包的可选类型,以绕过我也不想做的初始化. (3认同)
  • 我遇到过同样的问题.使用"required init"是有意义的,但swift并不是我所希望的"简单"语言.所有这些"可选"使语言比所需的更复杂.并且不支持DSL和AOP.我越来越失望了. (2认同)
  • 是的,我完全同意.很多我的房产现在被宣布为期权,因为我被迫这样做,真的不应该被允许为零.有些是可选项,因为它们合法地应该是可选项(意味着nil是有效值).然后在我不是子类的类中,我不需要使用选项,所以事情变得非常复杂,我似乎找不到合适的编码风格.希望Apple能够解决问题. (2认同)