博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

Objective-C类的加载和初始化

Posted on 2022-07-22 23:29  pencilCool  阅读(63)  评论(0编辑  收藏  举报

翻译自:https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html

Objective-C运行时使用两种方法来提供这种功能。+initialize和+load。

+load

如果类实现了这个方法,那么+load就会在实际加载时被调用。这发生在很早的时候。如果你在一个应用程序或一个应用程序链接的框架中实现了+load,+load将在main()之前运行。如果你在一个可加载的包中实现+load,那么它将在包的加载过程中运行。

使用+load可能很棘手,因为它运行得太早。很明显,有些类需要在其他类之前被加载,所以你不能确定你的其他类是否已经调用了+load。

比这更糟糕的是,你的应用程序(或框架或插件)中的C++静态初始化器还没有运行,所以如果你运行任何依赖它的代码,它很可能会崩溃。

好消息是,你所链接的框架保证在这时已经完全加载,所以使用框架类是安全的。

你的超类也被保证是完全加载的,所以它们也可以安全使用。请记住,

在加载时没有自动释放池(通常),所以如果你要调用Objective-C的东西,你需要把你的代码包在其中。

+load的一个有趣的特点是,它被运行时特例化,可以在实现它的类别中和主类一样被调用。

这意味着,如果你在一个类和该类的一个类别中实现了+load,那么这两个类都会被调用。这可能违背了你对类的工作原理的所有了解,但这是因为+load不是一个普通的方法。

这个特性意味着+load是一个很好的地方,可以做一些邪恶的事情,比如说方法交换。

+initialize

+initialize方法是在一个更理智的环境中调用的,通常是一个比+load更好的放置代码的地方。+initialize很有趣,因为它的调用很懒散,可能根本就不会被调用。当一个类第一次加载时,+initialize不被调用。当一个消息被发送到一个类时,运行时首先检查+initialize是否已经被调用。如果没有,它就会在继续发送消息之前调用它。从概念上讲,你可以把它看作是这样工作的。


 id objc_msgSend(id self, SEL _cmd, ...)
    {
        if(!self->class->initialized)
            [self->class initialize];
        ...send the message...
    }

当然,由于线程安全和其他许多有趣的事情,它要比这复杂得多,但这是基本的想法。+initialize在每个类中发生一次,并且在第一次有消息被发送到该类时发生。像+load一样,+initialize在发送给类本身之前,总是发送给该类的所有超类。
这使得+initialize的使用更加安全,因为它通常是在一个更加宽松的环境中被调用。显然,环境取决于第一次信息发送发生的确切时间,但几乎可以肯定的是,它至少是在你调用NSApplicationMain()之后。

因为+initialize的运行是懒散的,它显然不是一个放代码来注册一个不会被使用的类的好地方。例如,NSValueTransformer或NSURLProtocol子类不能使用+initialize来向它们的超类注册

不过,这使得它成为了做其他几乎所有类加载的好地方。它在一个更宽松的环境中运行的事实意味着你可以更自由地编写代码,而它懒散地运行的事实意味着你不会浪费资源来设置你的类,直到你的类真正被使用。

还有一个+initialize的技巧。如果这个类没有实现它,超类的+initialize将代替它运行。这正是所发生的事情。因为这个原因,你应该总是把你的+initialize方法写成这样。

   + (void)initialize
    {
        if(self == [WhateverClass class])
        {
            ...perform initialization...
        }
    }

如果没有这个额外的检查,你的初始化可能会运行两次,如果你有一个子类没有实现它自己的+initialize方法。这不仅仅是一个理论上的问题,即使你不写任何子类。苹果的Key-Value Observing创造了动态子类,它们没有覆盖+initialize。

结论

Objective-C提供了两种方法来自动运行类的设置代码。+load方法可以保证很早就运行,只要一个类被加载,对于那些必须很早就运行的代码是很有用的。这也使得它很危险,因为它的运行环境不是很友好。

对于大多数设置任务来说,+initialize方法要好得多,因为它在一个良好的环境中懒散地运行。你几乎可以在这里做任何你想做的事情,只要它不需要在某个外部实体给你的类发消息之前发生。