Swift 中的全局初始化与 viewDidLoad 初始化之间有区别吗

Is there a difference between global Initialization vs viewDidLoad initialization in Swift

如果我有一个 class 并像这样初始化一个变量:

class TestClass: UIViewController {
    var thisInt: Int = 10
}

与这样的初始化有什么不同吗:

class TestClass: UIViewController {
    var thisInt: Int!

    override func viewDidLoad() {
        super.viewDidLoad()
        thisInt = 10
    }
}

我想我的主要问题在于何时进行全局初始化,以及是否存在一个时间比正常 iOS 编程调用更多的时间原生开发)。我知道在 viewDidLoad 中这样做会限制我使用 weak 或 optional,但我更关心任何其他差异。

是的,有区别。尽管您的两个示例在技术上都可行,并且您的第一个代码段是最常见的。

我将向您展示一个示例,当您实际需要按照第二个代码段中的描述实施它时:

class ImageDisplayingViewController: UIViewController {
    @IBOutlet weak var thumbImageView: UIImageView!

    var choosenImageTag: Int!
    var choosenImage: UIImage!

    override func viewDidLoad() {
        super.viewDidLoad()
        thumbImageView.tag = imageTag
        thumbImageView.image = image
    }
}

假设您有一个视图控制器,您可以在其中选择一个图像,然后导航到下一个视图控制器以显示该图像(和一个标签,以便像您的示例一样保留 Int)。您可以通过调用以下方式在 prepare(for segue:, sender:) 中传递该图像和标签:

destinationViewController.choosenImage = choosenImage
destinationViewController.choosenImageTag = 10

然后 ImageDisplayingViewController 将在 viewDidLoad() 方法中实际加载图像,您确定您的出口已被初始化。

如果您只是尝试直接在 prepare(for segue:, sender:) 方法中加载图像,您会遇到崩溃,因为插座尚未初始化。

destinationViewController.thumbImageView.image = choosenImage  // crash
destinationViewController.thumbImageView.tag = 10

您可以通过在视图控制器中添加 "computed" 属性 轻松找到答案:

class TestClass: UIViewController {
    let a = {()->Int in print("global initialization"); return 10 }()
}

并添加一个

print("didFinishLaunching")

在应用程序的委托 didFinishLaunchingWithOptions 方法中。

您将获得的订单是

global initialization
didFinishLaunching

这意味着应用程序生命周期开始之前的全局初始化器运行。

现在,为了更进一步,您可以添加一个包含以下内容的 main.swift 文件

print("Before UIApplicationMain")

UIApplicationMain(CommandLine.argc, unsafeBitCast(CommandLine.unsafeArgv, to: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>.self), nil, NSStringFromClass(AppDelegate.self))

并从您的 AppDelegate class 中删除(或评论)@UIApplicationMain 装饰。这将指示编译器使用 main.swift 中的代码进行应用程序初始化,而不是讨论中装饰器提供的默认行为(尽管我们提供了几乎相同的自定义实现)。

您将在第二种方法中得到的是

Before UIApplicationMain
global initialization
didFinishLaunching

这意味着实例属性代码在加载故事板时执行。

现在,为了更深入地了解,让我们试着找出静态变量和实例变量之间的区别。为此,我们将添加一个测试 class:

class PropertyInitializationTest {
    static let staticProp = {()->String in print("static initialization of InitializationTest"); return "" }()
    let instanceProp = {()->String in print("instance initialization of InitializationTest"); return "" }()

    init() {
        print("In initializer")
    }
}

,并更新 AppDelegate 的启动完成:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        print("didFinishLaunching")
        print("before instantiating InitializationTest")
        _ = PropertyInitializationTest()
        print("after instantiating InitializationTest")
        // Override point for customization after application launch.
        return true
    }

我们得到的输出是:

Before UIApplicationMain
global initialization
didFinishLaunching
before instantiating InitializationTest
instance initialization of InitializationTest
In initializer
after instantiating InitializationTest

,这证实了实例属性在实例化 class 时以及在任何初始化代码 运行s.

之前设置的事实

但是等等!静态 属性 呢?根本没有任何痕迹表明它已被初始化。看起来静态属性在定义上是惰性的,只有在第一次访问时才会初始化。

更新应用确实完成启动代码确认了这一点。

print("didFinishLaunching")
print("before instantiating InitializationTest")
_ = PropertyInitializationTest()
print("after instantiating InitializationTest")
_ = PropertyInitializationTest.staticProp
print("after instantiating InitializationTest")

给出以下输出:

Before UIApplicationMain
global initialization
didFinishLaunching
before instantiating InitializationTest
instance initialization of InitializationTest
In initializer
after instantiating InitializationTest
static initialization of InitializationTest
after instantiating InitializationTest

总结:

  • 实例属性在 class 初始化程序 运行 之前接收编译时值(如果设置),因此在 class 的任何代码被执行之前
  • 静态属性只有在首次访问时才会收到它们的值,它们本质上是惰性的