NSObject + loadおよび+ initialize-彼らは何をしますか?


115

+ initializeまたは+ loadをオーバーライドするように開発者を導く状況を理解することに興味があります。ドキュメントは、これらのメソッドがObjective-Cランタイムによって呼び出されることを明確にしていますが、これらのメソッドのドキュメントから明らかなのはこれだけです。:-)

私の好奇心は、Appleのサンプルコード-MVCNetworkingを見ることにあります。モデルクラスには+(void) applicationStartupメソッドがあります。ファイルシステムのハウスキーピング、NSDefaultsの読み取りなどを行います。NSObjectのクラスメソッドを読み取ろうとすると、この管理作業は+ loadに入れても問題ないようです。

私はMVCNetworkingプロジェクトを変更し、App Delegateの+ applicationStartupへの呼び出しを削除し、ハウスキーピングビットを+ loadに入れました...私のコンピューターは発火しませんでしたが、それが正しいというわけではありません!+ loadまたは+ initializeと対比して呼び出さなければならないカスタムセットアップメソッドについて、微妙な点、注意点、および注意点について理解を深めたいと思っています。


+ loadのドキュメントの場合:

ロードメッセージは、動的にロードされ、静的にリンクされたクラスとカテゴリに送信されますが、新しくロードされたクラスまたはカテゴリが応答可能なメソッドを実装している場合のみです。

すべての単語の正確な意味がわからない場合、この文は扱いにくく、解析が困難です。助けて!

  • 「動的にロードされ、静的にリンクされた」とはどういう意味ですか?何かを動的にロードして静的にリンクできますか、それとも相互に排他的ですか?

  • 「...新しくロードされたクラスまたはカテゴリは、応答できるメソッドを実装しています」どのメソッドですか?どのように応答しますか?


+ initializeに関しては、ドキュメントには次のように書かれています:

初期化は、クラスごとに1回だけ呼び出されます。クラスおよびクラスのカテゴリに対して独立した初期化を実行する場合は、ロードメソッドを実装する必要があります。

これは、「クラスを設定しようとしている場合は...初期化を使用しないでください」という意味です。じゃ、いいよ。いつまたはなぜ私は初期化をオーバーライドしますか?

回答:


184

loadメッセージ

ランタイムloadは、クラスオブジェクトがプロセスのアドレス空間に読み込まれた直後に、各クラスオブジェクトにメッセージを送信します。プログラムの実行可能ファイルの一部であるクラスの場合、ランタイムloadはプロセスの存続期間の非常に早い段階でメッセージを送信します。共有(動的にロードされる)ライブラリにあるクラスの場合、ランタイムは、共有ライブラリがプロセスのアドレス空間にロードされた直後にロードメッセージを送信します。

さらに、loadクラスオブジェクト自体がloadメソッドを実装している場合、ランタイムはクラスオブジェクトにのみ送信します。例:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)load {
    NSLog(@"in Superclass load");
}

@end

@implementation Subclass

// ... load not implemented in this class

@end

ランタイムはloadメッセージをSuperclassクラスオブジェクトに送信します。からメソッドを継承しても、メッセージをクラスオブジェクトに送信しませloadSubclassSubclassSuperclass

ランタイムは、クラスのすべてのスーパークラスオブジェクト(これらのスーパークラスオブジェクトがを実装している場合)およびリンク先の共有ライブラリ内のすべてのクラスオブジェクトにメッセージをload送信した後、メッセージをクラスオブジェクトに送信しloadますload。ただし、自分の実行可能ファイル内の他のどのクラスを受け取ったかはloadまだわかりません。

プロセスがアドレススペースにロードするすべてのクラスは、プロセスがクラスを他に使用しているかどうかに関係なくloadloadメソッドを実装している場合はメッセージを受け取ります。

あなたは、ランタイムがアップどのように見えるかを確認することができますloadでの特殊なケースのような方法を_class_getLoadMethodobjc-runtime-new.mm、そしてから直接それを呼び出しますcall_class_loadsobjc-loadmethod.mm

ランタイムloadは、同じクラスの複数のカテゴリがを実装している場合でも、ロードするすべてのカテゴリのメソッドを実行しloadます。これは異常です。通常、2つのカテゴリが同じクラスの同じメソッドを定義する場合、メソッドの1つが「勝ち」、使用され、もう1つのメソッドは呼び出されません。

initializeメソッド

ランタイムは、initialize最初のメッセージ(loadまたは以外initialize)をクラスオブジェクトまたはクラスのインスタンスに送信する直前に、クラスオブジェクトのメソッドを呼び出します。このメッセージは通常のメカニズムを使用して送信されるため、クラスがを実装していないが、実装initializeしているクラスから継承している場合、クラスはそのスーパークラスのを使用しますinitialize。ランタイムはinitializeまずクラスのすべてのスーパークラスにを送信します(スーパークラスがまだ送信されていない場合initialize)。

例:

@interface Superclass : NSObject
@end

@interface Subclass : Superclass
@end

@implementation Superclass

+ (void)initialize {
    NSLog(@"in Superclass initialize; self = %@", self);
}

@end

@implementation Subclass

// ... initialize not implemented in this class

@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Subclass *object = [[Subclass alloc] init];
    }
    return 0;
}

このプログラムは、2行の出力を出力します。

2012-11-10 16:18:38.984 testApp[7498:c07] in Superclass initialize; self = Superclass
2012-11-10 16:18:38.987 testApp[7498:c07] in Superclass initialize; self = Subclass

システムはinitializeメソッドを遅延送信するので、プログラムが実際にメッセージをクラス(またはサブクラス、あるいはクラスまたはサブクラスのインスタンス)に送信しない限り、クラスはメッセージを受信しません。そして、あなたが受け取るinitializeまでに、プロセス内のすべてのクラスはすでに受け取っているはずですload(適切な場合)。

標準的な実装方法initializeは次のとおりです。

@implementation Someclass

+ (void)initialize {
    if (self == [Someclass class]) {
        // do whatever
    }
}

このパターンのポイントSomeclassは、実装していないサブクラスがある場合に、自分自身を再初期化しないようにすることinitializeです。

ランタイムinitializeはの_class_initialize関数でメッセージを送信しますobjc-initialize.mmobjc_msgSend送信に使用していることがわかります。これは通常のメッセージ送信機能です。

参考文献

このトピックに関するマイクアッシュの金曜日のQ&Aご覧ください。


25
+loadカテゴリごとに個別に送信されることに注意してください。つまり、クラスのすべてのカテゴリに独自の+loadメソッドが含まれている可能性があります。
ジョナサングリンスパン

1
また、初期化されていないエンティティを参照しているため、必要に応じinitializeloadメソッドによって正しく呼び出されることにも注意してくださいload。これは(奇妙ですが、賢明ですが)initialize前に実行される可能性がありloadます!とにかく、それは私が観察したものです。これは、「そして、あなたが受け取るinitializeときまでに、プロセス内のすべてのクラスはload(適切な場合)すでに受け取っているはずです。
ベンジョン2014年

5
load最初に受け取ります。を実行しているinitialize間、受信する可能性loadがあります。
rob mayoff 2014年

1
@robmayoffそれぞれのメソッド内に、[super initialize]行と[super load]行を追加する必要はありませんか?
damithH 2015年

1
ランタイムがそれらのメッセージを送信する前に、ランタイムがすべてのスーパークラスにすでに両方のメッセージを送信しているため、これは通常は悪い考えです。
rob mayoff 2015年

17

それが意味することは+initializeカテゴリーでオーバーライドしないことです、あなたはおそらく何かを壊すでしょう。

+loadクラスまたはカテゴリが読み込まれるとすぐに+load、を実装するクラスまたはカテゴリごとに1回呼び出されます。「静的にリンクされている」とは、アプリのバイナリにコンパイルされていることを意味します。このようにコンパイルされたクラスのメソッドは、アプリの起動時、おそらくそれが入る前に実行されます。「動的に読み込まれる」とは、プラグインバンドルまたはへの呼び出しを介して読み込まれることを意味します。iOSを使用している場合は、そのケースを無視できます。+loadmain()dlopen()

+initializeメッセージがクラスに初めて送信されたとき、そのメッセージを処理する直前に呼び出されます。これは(明らかに)一度だけ起こります。+initializeカテゴリでオーバーライドすると、次の3つのいずれかが発生します。

  • カテゴリの実装が呼び出されても、クラスの実装は呼び出されません
  • 他の誰かのカテゴリ実装が呼び出されます。あなたが書いたものは何もしません
  • あなたのカテゴリーはまだロードされておらず、その実装は呼び出されません。

あなたがオーバーライドしてはならない理由はここにある+initializeカテゴリに-実際には試してみて、交換することは非常に危険だ任意のあなたが交換しているか、あなた自身の交換のかどうか、それ自体が別のカテゴリーによって消されることはないことを確認するものだからカテゴリに方法を。

ところで、考慮すべきもう1つの問題+initializeは、誰かがサブクラス化した場合、クラスに対して1回、各サブクラスに対して1回呼び出される可能性があることです。static変数の設定などを行っている場合は、それを防ぐdispatch_once()必要がありますself == [MyClass class]

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.