为什么UIViewController子类的Swift默认初始化器会两次初始化属性?

3
为什么UIViewController子类的Swift默认初始化器init()会初始化两次属性?UIView的子类也会出现同样的问题,但NSObject的直接子类则不会。
使用Parent(nibName: nil, bundle: nil)进行初始化可以解决该问题,而使用Parent()则不能。当我为Parent提供自定义初始化器时,它也能正常工作。
我知道如何解决这个问题,但我很好奇为什么这会发生。
将此代码复制到Xcode 6.0.1 Playground中即可重现此问题。
import UIKit

class Child {
    init() {
        println("Child init")
    }
}

class Parent: UIViewController {
    let child = Child()
}

// This way "Child init" is printed twice:
let parent = Parent()

// This way "Child init" is printed once:
//let parent = Parent(nibName: nil, bundle: nil)
更新: 当我定义了一个虚假的类,它有与 UIViewController 相似的初始化器,并将其用作 Parent 的超类时,两种初始化方式都可以工作,并且只打印一次“Child init”。
import UIKit

class Child {
    init() {
        println("Child init")
    }
}

class FakeViewController : UIResponder {
    init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {

    }

    convenience override init() {
        self.init(nibName: nil, bundle: nil)
    }
}

class Parent: FakeViewController {
    let child = Child()
}

// With the FakeViewController both initializers cause "Child init" to be printed once:
let parent = Parent()
//let parent = Parent(nibName: nil, bundle: nil)


  • UIViewController的便利初始化方法(convenience init())是否应该以这种方式工作?
  • UIViewController的便利初始化方法(convenience init())的实现中是否存在bug?
  • 对于UIViewController来说,init()是一个有效的初始化器吗?也许不是,第一个示例中的let parent = Parent()甚至不应该编译通过?

请参阅UIViewController的文档,以及您在子类化它时需要提供的内容。通过插入FakeViewController,您正在使用以下建议来解决问题。 - AlBlue
3个回答

4

第一次打印发生在构造父实例时,所有实例字段都在那个时候初始化,这包括创建子实例。

第二次打印发生在调用父类的隐式super.init时。由于此代码是封闭的,无法确定确切发生了什么;但问题可能源于init是UIViewController中的方便初始化程序(指定初始化程序为init:nibName:bundle)。UIViewController文档规定,当它被重写时,必须调用所需的初始化程序。

因此,要更正此错误,您需要添加:

class Parent: UIViewController {
  override init() {
    super.init(nibName:nil,bundle:nil)
  }
  // the following is also required if implementing an initializer
  required init(coder:NSCoder) {
    super.init(coder:coder)
  }
}

请查看https://developer.apple.com/library/prerelease/iOS/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-XID_319,了解有关指定初始化程序与便捷初始化程序的更多信息。

我在"override init() {"这一行遇到了错误:"Initializer does not override a designated initializer from its superclass"。 - user1021430

1
这个问题已经在Xcode 6.3中得到解决。我能重现这个bug的最后一个版本是Xcode 6.2。

1

显然,UIViewContollerinit 实现方式如下:

- (instancetype)init {
    self = [super init]; // <- not sure
    if(self) {
        self = [[self.class alloc] initWithNibName:nil bundle:nil];
    }
    return self;
}

你可以使用调试器查看,在第一次和第二次调用Child()时,Parentself地址不同。
在Swift中,属性是在所有者对象初始化之前初始化的。这就是为什么你的Child()会被调用两次的原因。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接