【问题标题】:Is there a difference between global Initialization vs viewDidLoad initialization in SwiftSwift中的全局初始化与viewDidLoad初始化之间有区别吗
【发布时间】:2016-12-29 22:59:36
【问题描述】:

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

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

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

class TestClass: UIViewController {
    var thisInt: Int!

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

我想我的主要问题在于全局初始化何时发生,并且是否有一段时间会调用一个比正常 iOS 编程调用另一个更多的时间(不做任何反对原生开发的事情)。我确实理解在 viewDidLoad 中执行此操作会限制我使用弱或可选,但我更关心任何其他差异。

【问题讨论】:

    标签: swift scope initialization viewdidload


    【解决方案1】:

    您可以通过向视图控制器添加“计算”属性来轻松找到:

    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 类中删除(或注释)@UIApplicationMain 装饰。这将指示编译器使用 main.swift 中的代码进行应用程序初始化,而不是讨论中装饰器提供的默认行为(尽管我们提供了几乎相同的自定义实现)。

    你会在第二种方法中得到的是

    Before UIApplicationMain
    global initialization
    didFinishLaunching
    

    表示加载情节提要时执行实例属性代码。

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

    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
    

    ,它确认实例属性在类被实例化时以及在任何初始化程序代码运行之前被设置。

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

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

    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
    

    总结:

    • 实例属性在类初始化程序运行之前接收编译时值(如果已设置),因此在该类的任何代码执行之前
    • 静态属性仅在第一次访问时接收它们的值,它们本质上是惰性的

    【讨论】:

    • 好答案!!!所以,这回答了我的一半问题。另一半,是否曾经在没有 viewDidLoad 的情况下触发全局初始化,或者 viewDidLoad 会在没有对它们所在的类进行全局初始化的情况下触发?这是假设它不会在全局初始化过程中崩溃。
    • @Sethmr 我用更多结果更新了我的答案。结论是viewDidLoad 代码将始终在全局初始化之后运行,因为这是运行时在实例化类时所做的设置的一部分
    • 感谢您的宝贵时间。我可以深入研究这样的东西,但是从有更深入了解的人那里听到它会更容易成长,并看到未来解决这些问题的最佳方法。
    • 这给我留下的一个问题是......它如何知道带有全局变量的类将被调用多少次,以便为类的每个实例化初始化它的数据?跨度>
    • 运行时不必知道它会创建多少个实例。编译器为属性初始化创建了代码,并且在实例化类时由运行时执行该代码。
    【解决方案2】:

    是的,有区别。尽管您的两个示例在技术上都可以工作,并且您的第一个 sn-p 是最常见的。

    我将向您展示一个示例,当您实际上需要按照第二个 sn-p 中的描述实现它时:

    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
    

    【讨论】:

    • 感谢您抽出宝贵时间,让您投票。我一直在寻找更多关注第一个示例何时初始化的信息,以及是否存在这样的情况:如果您有两种方法使用不同的变量,则可以再次初始化一个变量而不会调用另一个变量。
    • 我作为一名 iOS 开发人员已经工作了一年多,我觉得我现在几乎可以编写任何程序。似乎我对堆栈溢出的所有问题都完全不同,并且没有像我希望的那样得到回答。我认为大多数人提出的问题类型表明 95% 以上的时间缺乏研究。
    猜你喜欢
    • 2010-12-11
    • 2010-11-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多