web-dev-qa-db-ja.com

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

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

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

MVCNetworkingプロジェクトを変更し、App Delegateの呼び出しを+ applicationStartupに削除し、Housekeepingビットを+ loadに入れました...私のコンピューターは発火しませんでしたが、それは正しいという意味ではありません! '微妙な点、落とし穴、および+ loadまたは+ initializeに対して呼び出す必要があるカスタムセットアップメソッドの周囲のその他のことを理解したいと考えています。


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

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

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

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

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


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

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

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

112
edelaney05

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クラスオブジェクトに送信します。 loadは継承しますが、SubclassクラスオブジェクトにSubclassメッセージを送信しますnotSuperclassのメソッド。

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

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

_class_getLoadMethodobjc-runtime-new.mmで、ランタイムがloadメソッドを特別なケースとして検索し、 call_class_loadsobjc-loadmethod.mmから直接呼び出す方法を確認できます。

ランタイムは、同じクラスの複数のカテゴリがloadを実装している場合でも、ロードするすべてのカテゴリのloadメソッドも実行します。これは異常です。通常、2つのカテゴリが同じクラスで同じメソッドを定義する場合、メソッドの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が自身を再初期化しないようにすることです。

ランタイムは、 _class_initializeobjc-initialize.mm関数でinitializeメッセージを送信します。 objc_msgSendを使用して送信することがわかります。これは通常のメッセージ送信関数です。

参考文献

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

181
rob mayoff

つまり、カテゴリ内の_+initialize_をオーバーライドしないでください。おそらく何かを壊してしまうでしょう。

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

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

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

これが、カテゴリ内で_+initialize_を決してオーバーライドしてはならない理由です。実際、カテゴリ内でanyメソッドを試して置き換えるのは非常に危険です。独自の置換は、別のカテゴリによって切り替えられます。

ところで、_+initialize_で考慮すべきもう1つの問題は、誰かがあなたをサブクラス化すると、クラスとサブクラスごとに1回呼び出される可能性があることです。 static変数の設定などを行う場合は、dispatch_once()を使用するか、_self == [MyClass class]_をテストすることで、これを防ぐ必要があります。

17
user23743