译文 · 原文: Friday Q&A 2009-05-22: Objective-C Class Loading and Initialization · 作者 Mike Ash
原文:https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html 发布:2009-05-22 作者:Mike Ash 译者:MiMo(mimo-v2.5-pro);代码块保留英文原样
欢迎回来继续收听又一期精彩的周五问答。休息几周后,我打算恢复定期更新。这个意图能坚持多久还有待观察,但我对此持乐观态度。本周我将采纳丹尼尔・雅尔库特的建议,探讨 Objective-C 中的类加载与初始化。
在 Objective-C 中,类如何实际加载到内存里,通常并非程序员需要操心的问题。这是一系列复杂的流程,由 ** 运行时链接器(runtime linker)** 处理,并且在你的代码开始运行前早已完成。
对大多数类而言,你只需了解这些就够了。但有些类需要执行更多操作 —— 实际上要运行某些代码来完成特定设置。一个类可能需要初始化全局表(global table)、缓存用户默认设置中的数值,或执行其他各类任务。
Objective-C 运行时通过两个方法来实现这种功能:+initialize和+load。
+load
+load 方法会在类实际被加载时调用,前提是该类实现了这个方法。这个过程发生得非常早。如果你在应用程序或应用程序链接的框架中实现了 +load,那么它将在 main () 函数之前运行。如果你在可加载束(loadable bundle)中实现了 +load,那么它会在束的加载过程中运行。
使用 +load 可能会比较棘手,因为它运行得太早了。显然,某些类需要在其他类之前加载,因此你无法确定你的其他类是否已经调用了 +load。更糟糕的是,你的应用程序(或框架或插件)中的 C++ 静态初始化器(static initializer)尚未运行,所以如果你运行任何依赖于此的代码,很可能会导致崩溃。好消息是,你链接到的框架(framework)在此时已保证被完全加载,因此使用框架类是安全的。你的父类(superclass)也保证已被完全加载,所以它们也是安全的。请记住,(通常)在加载时没有自动释放池(autorelease pool),因此如果你要调用 Objective-C 相关的代码,就需要用一个池子将你的代码包裹起来。
+load 的一个有趣特性是,运行时(runtime)会专门处理它,使其在实现它的分类(category)以及主类中均被调用。这意味着如果你在一个类及其分类中都实现了 +load,两者都会被调用。这可能与你所知的分类工作方式相悖,但这恰恰是因为 +load 并非普通方法。此特性使得 +load 成为执行方法调配(method swizzling)等 “黑魔法” 的理想场所。
+initialize 该方法在更合理的环境中被调用,通常比 +load 更适合放置代码。+initialize 的特殊性在于它是惰性调用的,甚至可能根本不会被调用。当类首次加载时,+initialize 并不会被调用。只有当向该类发送消息时,运行时才会首先检查 +initialize 是否已被调用。若未调用,则会在继续消息发送之前先调用它。从概念上理解,其运作方式如下:
id objc_msgSend(id self, SEL _cmd, ...) { if(!self->class->initialized) [self->class initialize]; ...send the message... }这使得 +initialize 的使用更加安全,因为它通常在一个宽容得多的环境中被调用。显然,该环境具体取决于首次消息发送(message sending)的确切时机,但几乎可以确定它至少会在调用 NSApplicationMain () 之后发生。
由于 +initialize 是惰性调用的,所以它显然不是放置代码来注册一个原本不会被使用的类的理想场所。例如,NSValueTransformer 或 NSURLProtocol 的子类不能使用 +initialize 向其超类注册,因为这会陷入鸡生蛋还是蛋生鸡的困境。
然而,就类加载(class loading)而言,这使得它成为执行几乎所有其他操作的绝佳场所。它运行在更宽容的环境这一事实意味着你可以在编写的代码中拥有更大的自由度;而它惰性调用的特性则意味着,直到你的类真正被使用时,才会浪费资源进行设置。
关于 +initialize 还有一个技巧。我在上面的伪代码中写道 runtime 会执行 [self->class initialize]。这意味着正常 Objective-C 分发语系(dispatch semantics)会生效 —— 如果该类没有实现此方法,其父类(superclass)的 +initialize 就会被执行。实际情况正是如此。因此,你应该始终按照如下模式编写 +initialize 方法:
+ (void)initialize { if (self == [ClassName class]) { // 初始化代码 }} + (void)initialize { if(self == [WhateverClass class]) { ...perform initialization... } }结论
Objective-C 提供了两种自动运行类设置代码的方式。+load 方法(类加载时调用的方法)保证在非常早期运行,一旦类被加载就会执行,这对必须在早期运行的代码很有用。但也因此使其变得危险,因为其运行环境并非十分友好。
对于大多数设置任务,+initialize 方法(惰性调用的初始化方法)是更合适的选择,因为它惰性运行且环境良好。只要这些操作不需要在外部实体向你的类发送消息之前发生,你几乎可以在这里做任何事情。
本周的 Friday Q & A 至此结束。下周请继续关注另一期精彩的专题。一如既往,请通过电子邮件发送你的建议或在下方留言。没有你们宝贵的建议,Friday Q & A 就无法持续运营,所以今天就请发送你的建议吧!
Original (English)
Welcome back to another cromulent Friday Q&A. After taking a few weeks off I intend to resume the regular schedule. We’ll see how far that intention takes me, but I’m hopeful. This week I’m going to take Daniel Jalkut’s suggestion to discuss class loading and initialization in Objective-C.
How classes actually get loaded into memory in Objective-C aren’t anything that you, the programmer, need to worry about most of the time. It’s a bunch of complicated stuff that’s handled by the runtime linker and is long done before your code ever starts to run.
For most classes, that’s all you need to worry about. But some classes need to do more, and actually run some code in order to perform some kind of setup. A class may need to initialize a global table, cache values from user defaults, or do any number of other tasks.
The Objective-C runtime uses two methods to provide this functionality: +initialize and +load.
+load +load is invoked as the class is actually loaded, if it implements the method. This happens very early on. If you implement +load in an application or in a framework that an application links to, +load will run before main(). If you implement +load in a loadable bundle, then it runs during the bundle loading process.
Using +load can be tricky because it runs so early. Obviously some classes need to be loaded before others, so you can’t be sure that your other classes have had +load invoked yet. Worse than this, C++ static initializers in your app (or framework or plugin) won’t have run yet, so if you run any code that relies on that it will likely crash. The good news is that frameworks you link to are guaranteed to be fully loaded by this point, so it’s safe to use framework classes. Your superclasses are also guaranteed to be fully loaded, so they are safe to use as well. Keep in mind that there’s no autorelease pool present at loading time (usually) so you’ll need to wrap your code in one if you’re calling into Objective-C stuff.
An interesting feature of +load is that it’s special-cased by the runtime to be invoked in categories which implement it as well as the main class. This means that if you implement +load in a class and in a category on that class, both will be called. This probably goes against everything you know about how categories work, but that’s because +load is not a normal method. This feature means that +load is an excellent place to do evil things like method swizzling.
+initialize The +initialize method is invoked in a more sane environment and is usually a better place to put code than +load. +initialize is interesting because it’s invoked lazily and may not be invoked at all. When a class first loads, +initialize is not called. When a message is sent to a class, the runtime first checks to see if +initialize has been called yet. If not, it calls it before proceeding with the message send. Conceptually, you can think of it as working like this:
id objc_msgSend(id self, SEL _cmd, ...) { if(!self->class->initialized) [self->class initialize]; ...send the message... }This makes +initialize safer to use because it’s usually called in a much more forgiving environment. Obviously the environment depends on exactly when that first message send happens, but it’s virtually certain to at least be after your call to NSApplicationMain().
Because +initialize runs lazily, it’s obviously not a good place to put code to register a class that otherwise wouldn’t get used. For example, NSValueTransformer or NSURLProtocol subclasses can’t use +initialize to register themselves with their superclasses, because you set up a chicken-and-egg situation.
This makes it a good place to do virtually everything else as far as class loading goes, though. The fact that it runs in a much more forgiving environment means you can be much freer with the code you write, and the fact that it runs lazily means that you don’t waste resources setting your class up until your class actually gets used.
There’s one more trick to +initialize. In my pseudocode above I wrote that the runtime does [self->class initialize]. This implies that normal Objective-C dispatch semantics apply, and that if the class doesn’t implement it, the superclass’s +initialize will run instead. That’s exactly what happens. Because of this, you should always write your +initialize method to look like this:
+ (void)initialize { if(self == [WhateverClass class]) { ...perform initialization... } }Conclusion Objective-C offers two ways to automatically run class-setup code. The +load method is guaranteed to run very early, as soon as a class is loaded, and is useful for code that must also run very early. This also makes it dangerous, as it’s not a very friendly environment to run it.
The +initialize method is much nicer for most setup tasks, because it runs lazily and in a nice environment. You can do pretty much anything you want from here, as long as it doesn’t need to happen until some external entity messages your class.
That wraps up Friday Q&A for this week. Come back next week for another exciting edition. As always, e-mail your suggestions or post them below. Without your valuable contribution of ideas, Friday Q&A can’t operate, so send yours in today!