123

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

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

私は MVCNetworking プロジェクトを変更し、App Delegate の +applicationStartup への呼び出しを削除し、ハウスキーピング ビットを +load に配置しました... 私のコンピューターは発火しませんでしたが、それが正しいというわけではありません! +load または +initialize に対して呼び出す必要があるカスタム セットアップ メソッドに関する微妙な点、落とし穴、およびその他の事項について理解を深めたいと考えています。


+load ドキュメントについては次のように述べています。

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

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

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

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


+initialize に関しては、ドキュメントには次のように記載されています。

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

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

4

2 に答える 2

193

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を送信した後、クラスオブジェクトにメッセージを送信します。ただし、自分の実行可能ファイル内の他のどのクラスがまだ受信しているかはわかりません。loadloadload

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

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

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

initialize方法_

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

例:

@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_initializeobjc-initialize.mmobjc_msgSend

参考文献

このトピックに関するMikeAshの金曜日のQ&Aを確認してください。

于 2012-11-10T22:23:05.567 に答える
17

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

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

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

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

これが、カテゴリでオーバーライドしてはならない理由です。実際、何を置き換えるのか、または自分の置き換え自体が別のカテゴリに切り替わるのかわからないため、カテゴリ内のメソッド+initialize置き換えようとするのは非常に危険です。

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

于 2012-11-10T22:28:50.290 に答える