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

时间:2014-08-04 19:41:43

标签: ios swift sprite-kit

所以我今天更新到Xcode 6 beta 5并注意到我在几乎所有Apple类的子类中都收到了错误。

错误说明:

  

Class'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)

    }

}

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

4 个答案:

答案 0 :(得分:128)

来自开发者论坛上的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)
}

答案 1 :(得分:69)

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

  1. 如果协议将初始值设定项指定为必需方法,则必须使用Swift的required关键字标记该初始值设定项。
  2. Swift有一套关于init方法的特殊继承规则。
  3. tl; dr 是:

      

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

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

    那么......为长版本准备好了吗?

    Swift有一组关于init方法的特殊继承规则。

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

    我在本回答的这一部分介绍的所有信息均来自Apple的文档here

    来自Apple文档:

      

    与Objective-C中的子类不同,默认情况下Swift子类不会继承它们的超类初始化器。 Swift的方法可以防止来自超类的简单初始化程序被更专业的子类继承的情况,并且用于创建未完全或正确初始化的子类的新实例。

    强调我的。

    所以,直接来自那里的Apple文档,我们发现Swift子类并不总是(并且通常不会)继承他们的超类init方法。

    那么,他们什么时候从他们的超类继承?

    有两个规则定义子类何时从其父级继承init方法。来自Apple文档:

      

    规则1

         

    如果您的子类没有定义任何指定的初始值设定项,它会自动继承其所有超类指定的初始值设定项。

         

    规则2

         

    如果您的子类提供了所有超类指定初始化器的实现 - 通过按照规则1继承它们,或者通过提供自定义实现作为其定义的一部分 - 那么它会自动继承所有超类便捷初始化器。 / p>

    规则2与此对话并不特别相关,因为SKSpriteNode init(coder: NSCoder)不太可能是一种便捷方法。

    因此,您的InfoBar班级一直在继承required初始化程序,直到您添加init(team: Team, size: CGSize)为止。

    如果您没有提供此init方法,而是将InfoBar添加的属性设为可选或为其提供默认值,那么您仍然可以继承SKSpriteNode' s init(coder: NSCoder)。但是,当我们添加自己的自定义初始值设定项时,我们停止继承超类的指定初始值设定项(以及convenience initializers并没有指向我们实现的初始值设定项。)

    所以,作为一个简单的例子,我提出这个:

    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'在电话中。

    enter image description here

    如果这是Objective-C,它继承没有问题。如果我们在Objective-C中使用Bar初始化initWithFoo:,则self.bar属性将只是nil。它可能不是很好,但它对于要进入的对象来说是完全有效的状态。它的是完全有效的状态要进入的Swift对象。self.bar不是可选的,不能是nil

    同样,我们继承初始化器的唯一方法是不提供我们自己的方法。因此,如果我们尝试通过删除Bar' s init(foo: String, bar: String)来继承,那么:

    class Bar: Foo {
        var bar: String
    }
    

    现在我们又回到继承(有点),但这不会编译......错误信息解释了为什么我们不继承超类init方法:

      

    问题: Class' Bar'没有初始化者

         

    修复它:存储的属性' bar'没有初始化器会阻止合成初始化器

    如果我们在子类中添加了存储的属性,那么就没有可能的Swift方法来创建我们的子类的有效实例,而超类初始化器也不可能知道我们的子类&#39 ; s存储的属性。

    好吧,为什么我必须实施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'

    中的初始化程序      

    修复它:插入必需的

    它要我生成init(foo: Int)初始值设定项。我也可以通过创建课程final(意味着课程不能继承)来使它快乐。

    那么,如果我是子类会发生什么?从这一点来说,如果我是子类,我很好。如果我添加任何初始值设定项,我突然不再继承init(foo:)。这是有问题的,因为现在我不再符合InitProtocol。我不能从符合协议的类中继承子类,然后突然决定我不再想要符合该协议。我已经继承了协议一致性,但由于Swift使用init方法继承的方式,我没有继承符合该协议所需的部分内容,我必须实现它

    好的,这一切都有道理。但为什么我无法获得更有用的错误消息?

    可以说,如果错误消息指定您的类不再符合继承的NSCoding协议并且要修复它,则需要实现init(coder: NSCoder),则错误消息可能更明确或更好。肯定。

    但是Xcode根本无法生成该消息,因为实际上它并不总是不实现或继承所需方法的实际问题。除了协议一致性以及工厂方法之外,至少有一个其他原因可以使init方法required

    如果我想编写正确的工厂方法,我需要将返回类型指定为Self(Swift等同于Objective-C' s instanceType)。但为了做到这一点,我实际上需要使用required初始化方法。

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

    这会产生错误:

      

    构建类类型的对象' Self'具有元类型值必须使用' required'初始化

    enter image description here

    它基本上是同一个问题。如果我们继承Box,我们的子类将继承类方法factory。所以我们可以致电SubclassedBox.factory()。但是,如果required方法中没有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)
    

    无法编译。

    enter image description here

    它给出的错误信息有点误导:

      

    额外的论点' b'在电话中

    但问题是,Bar并未继承任何Foo init方法,因为它并不满足这两种特殊方法中的任何一种从父类继承init方法的案例。

    如果这是Objective-C,我们会毫无问题地继承init,因为Objective-C非常乐意不初始化对象'属性(虽然作为开发人员,你不应该对此感到满意)。在Swift中,这根本就不行。您不能拥有无效状态,并且继承超类初始值设定项只会导致无效的对象状态。

答案 2 :(得分:56)

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

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

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

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

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

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

答案 3 :(得分:33)

添加

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