2017年10月20日金曜日

クラスメソッド +initilalize について

+initializeが複数回呼ばれることがあるため、[super initialize]を呼ぶべきかどうかも含めて調べてみた。
結論としては、[super initialize]は呼ぶ必要はないが、空でも+initializeを実装しておいた方が良い場合がある。

クラスが初めて参照された時に+initializeが呼ばれるが、あるサブクラスのインスタンスを作成すると、そのクラス階層の上位からinitializeが呼ばれる。

例えば、次のようなクラス階層を作る。


 @interface MyClass1 : NSObject
 @interface MyClass2 : MyClass1
 @interface MyClass3 : MyClass2
各々に次のような+initializeメソッドを実装する。
 + (void)initialize
 {
     NSLog(@"MyClass1.initialize");
 }

どこかでMyClass3のインスタンスを作る。
 MyClass3 *myClass3 = [[MyClass3 alloc] init];

この場合、次のようにログが書き出され、クラス階層の上位から+initializeメソッドが呼ばれていることがわかる。
 MyClass1.initialize
 MyClass2.initialize
 MyClass3.initialize

次のように順次上位のクラスのインスタンスを作ってみる。
 MyClass1 *myClass1 = [[MyClass1 alloc] init];
 NSLog(@"%@", myClass1);
 MyClass2 *myClass2 = [[MyClass2 alloc] init];
 NSLog(@"%@", myClass2);
 MyClass3 *myClass3 = [[MyClass3 alloc] init];

 NSLog(@"%@", myClass3);

この場合は次のようにログが書き出される。
 MyClass1.initialize
 <MyClass1: 0x6000000089c0>
 MyClass2.initialize
 <MyClass2: 0x600000008940>
 MyClass3.initialize

 <MyClass3: 0x6000000089d0>

すでに+initializeが呼ばれているクラスについては、繰り返し+initializeが呼ばれない。

ちなみに次の順序でインスンタンスを作ると、
 MyClass3 *myClass3 = [[MyClass3 alloc] init];
 NSLog(@"%@", myClass3);
 MyClass1 *myClass1 = [[MyClass1 alloc] init];
 NSLog(@"%@", myClass1);
 MyClass2 *myClass2 = [[MyClass2 alloc] init];

 NSLog(@"%@", myClass2);

ログは次のようになる。
 MyClass1.initialize
 MyClass2.initialize
 MyClass3.initialize
 <MyClass3: 0x60000000aa80>
 <MyClass1: 0x60000000aad0>

 <MyClass2: 0x60000000aae0>


myClass3を作った時にMyClass1、MyClass2、MyClass3の+initializeが順次呼ばれており、それ以降のmyClass1、myClass2作成時には呼ばれない。



さて、+initializeメソッドで[super initialize]を呼ぶように変えてみる。
 + (void)initialize
 {
     [super initialize];
     NSLog(@"MyClass1.initialize");
 }

すると、
 MyClass3 *myClass3 = [[MyClass3 allocinit];

の場合に、次のようにinitializeが呼ばれるようになる。
 MyClass1.initialize
 MyClass1.initialize
 MyClass2.initialize
 MyClass1.initialize
 MyClass2.initialize
 MyClass3.initialize

MyClass1、MyClass2、MyClass3の順に+initializeが呼ばれるが、MyClass2の時にsuperのMyClass1が再度呼ばれ、MyClass3の時に同様にMyClass2、MyClass1が再度呼ばれる。

ということで、[super initialize]を呼ぶと無駄な呼び出しが繰り返されることになり、実装によっては不具合を生じる可能性もある。

さてここで、[super initialize]は削除し、さらにMyClass3の+initializeを削除してみる。

すると、
 MyClass3 *myClass3 = [[MyClass3 allocinit];

の場合のログは次のようになる。
 MyClass1.initialize
 MyClass2.initialize
 MyClass2.initialize

superクラスのMyClass1、MyClass2の+initializeが順次呼ばれたあと、myClass3の+initializeが呼ばれようとするが、これが実装されていない場合はそのsuperクラスのinitializeが呼ばれる。

サブクラスが+initializeを実装していない場合、そのsuperクラスのinitializeが複数回呼ばれることになる。

MyClass2には+initializeが実装されているが[super initialize]はないため、MyClass1はここでは再度呼ばれない。

さらにMyClass2の+initializeを削除すると、ログは次のようになる。
 MyClass1.initialize
 MyClass1.initialize
 MyClass1.initialize

myClass2、myClass3は+initializeがないのでログは書かないが、+initializeがないのでsuperを辿り、MyClass1の+initializeが実行され、MyClass1のログが3回書かれる。

このようにsuperの+initializeを実行するのは、ダイナミックにクラスを生成する場合の初期化に必要なためらしい。

ということで、[super initialize]を書かなくても上位のクラスから+initializeが呼ばれるので、書く必要はない。むしろ書かない方が良い。

一方、+initializeを実装しない場合は上位クラスの+initializeが複数回呼ばれる。サブクラスが必ずしも+initializeを実装するとは限らないので、+initializeでは複数回呼ばれても問題ないような実装にする必要がある。また、特別な理由がなければ、空の+initialize 
を実装しておいた方が良いように思える。

もし+initializeが一度だけしか実行されないようにする必要がある場合は、次のような方法がある。
 + (void)initialize
 {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        ......
    });
 }

2017年10月15日日曜日

Could not load the "imageName.png" image referenced from a nib in the bundle with identifier ...

Could not load the "imageName.png" image referenced from a nib in the bundle with identifier ...

pngをプロヘクトに追加するとInterfaceBuilderではUIにimageを設定できるようになるが、それだけではビルド時にbundleにコピーされないため、実行時にリソースが見つけられず発生する。

プロジェクトビュー > Target > BuildPhases > Copy Bundle Resources
エラーが発生している画像ファイルを追加する。