開発者が+ initializeまたは+ loadをオーバーライドする状況を理解することに興味があります。ドキュメントでは、これらのメソッドがObjective-Cランタイムによって呼び出されることが明確になっていますが、これらのメソッドのドキュメントからは本当にそれだけです。 :-)
私の好奇心は、AppleのサンプルコードであるMVCNetworkingを見ることです。モデルクラスには+(void) applicationStartup
メソッドがあります。ファイルシステムでいくつかのハウスキーピングを行い、NSDefaultsなどを読み取ります...そしてNSObjectのクラスメソッドを理解しようとした後、この管理作業は+ loadに入れてもいいようです。
MVCNetworkingプロジェクトを変更し、App Delegateの呼び出しを+ applicationStartupに削除し、Housekeepingビットを+ loadに入れました...私のコンピューターは発火しませんでしたが、それは正しいという意味ではありません! '微妙な点、落とし穴、および+ loadまたは+ initializeに対して呼び出す必要があるカスタムセットアップメソッドの周囲のその他のことを理解したいと考えています。
+ loadドキュメントの場合:
ロードメッセージは、動的にロードされ静的にリンクされたクラスとカテゴリに送信されますが、新しくロードされたクラスまたはカテゴリが応答可能なメソッドを実装している場合のみです。
すべての単語の正確な意味がわからない場合、この文はわかりにくく、解析が困難です。助けて!
「動的にロードされ、静的にリンクされた」とはどういう意味ですか?何かを動的にロードして静的にリンクできますか、それとも相互に排他的ですか?
「...新しくロードされたクラスまたはカテゴリは、応答できるメソッドを実装します」どのメソッドですか?どのように対応しますか?
+ initializeに関しては、ドキュメントには次のように書かれています:
初期化は、クラスごとに1回だけ呼び出されます。クラスおよびクラスのカテゴリに対して独立した初期化を実行する場合は、ロードメソッドを実装する必要があります。
これは、「クラスをセットアップしようとしている場合...初期化を使用しないでください」という意味です。じゃ、いいよ。いつ、またはなぜ初期化をオーバーライドしますか?
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_getLoadMethod
のobjc-runtime-new.mm
で、ランタイムがload
メソッドを特別なケースとして検索し、 call_class_loads
のobjc-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_initialize
のobjc-initialize.mm
関数でinitialize
メッセージを送信します。 objc_msgSend
を使用して送信することがわかります。これは通常のメッセージ送信関数です。
このトピックに関する マイクアッシュの金曜日のQ&A をご覧ください。
つまり、カテゴリ内の_+initialize
_をオーバーライドしないでください。おそらく何かを壊してしまうでしょう。
_+load
_は、_+load
_を実装するクラスまたはカテゴリごとに1回呼び出されます。すぐにそのクラスまたはカテゴリがロードされます。 「静的リンク」と表示されている場合、アプリのバイナリにコンパイルされていることを意味します。このようにコンパイルされたクラスの_+load
_メソッドは、おそらくmain()
に入る前に、アプリの起動時に実行されます。 「動的にロードされた」というときは、プラグインバンドルまたはdlopen()
の呼び出しによってロードされたことを意味します。 iOSを使用している場合は、そのケースを無視できます。
_+initialize
_は、メッセージがクラスに初めて送信されたときに、そのメッセージを処理する直前に呼び出されます。これは(明らかに)1回だけ発生します。カテゴリで_+initialize
_をオーバーライドすると、次の3つのいずれかが発生します。
これが、カテゴリ内で_+initialize
_を決してオーバーライドしてはならない理由です。実際、カテゴリ内でanyメソッドを試して置き換えるのは非常に危険です。独自の置換は、別のカテゴリによって切り替えられます。
ところで、_+initialize
_で考慮すべきもう1つの問題は、誰かがあなたをサブクラス化すると、クラスとサブクラスごとに1回呼び出される可能性があることです。 static
変数の設定などを行う場合は、dispatch_once()
を使用するか、_self == [MyClass class]
_をテストすることで、これを防ぐ必要があります。