Objective-Cでself = [super init]がnilでないことを確認する必要があるのはなぜですか?


165

Objective-Cでのinitメソッドの記述に関する一般的な質問があります。

初期化を続行する前に、initメソッドがself = [super init]がnilでないかどうかを確認する必要があることが、どこでも見られます(Appleのコード、書籍、オープンソースコードなど)。

initメソッドのデフォルトのAppleテンプレートは次のとおりです。

- (id) init
{
    self = [super init];

    if (self != nil)
    {
        // your code here
    }

    return self;
}

どうして?

initがnilを返すのはいつですか?NSObjectでinitを呼び出してnilが返された場合、何かが本当にねじ込まれているはずですよね?そしてその場合、あなたはプログラムを書くことすらできないかもしれません...

クラスのinitメソッドがnilを返すことは本当にありふれたことですか?もしそうなら、どのような場合に、そしてなぜ?


1
Wil Shipleyがこれに関連する記事をしばらく前に投稿しました。[self = [stupid init];](wilshipley.com/blog/2005/07/self-stupid-init.html)コメントも読んでください。
Ryan Townshend、

6
Wil ShipleyMike AshMatt Gallagherのいずれかに質問できます。いずれにせよ、それは議論されたトピックのようなものです。しかし、通常はAppleのイディオムに固執するのが良いです...結局のところ、それは彼らのフレームワークです。
jbrennan 2009

1
Wilは、[super init]がレシーバーを返さない可能性があることを知って、initの間に盲目的に自分自身を再割り当てしないための訴訟を起こしているようです。
ジャサリエン2009

3
ウィルは、その投稿が最初に行われて以来、考えを変えました。
bbum 2009

5
私はこの質問を少し前に見たことがありますが、それをもう一度見つけました。完璧です。+1
ダンローゼンスターク2010

回答:


53

例えば:

[[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"];
[[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"];
[NSImage imageNamed:@"AnImageThatIsntInTheImageCache"];

... 等々。(注:ファイルが存在しない場合、NSDataは例外をスローする可能性があります)。問題が発生したときにnilを返すことが期待される動作である領域はかなり多くあります。そのため、一貫性を保つために、常にnilをチェックするのが標準的な方法です。


10
はい。ただし、これはそれぞれのクラスのinitメソッド内ではありません。NSDataはNSObjectを継承しています。NSDataは[super init]がnilを返すかどうかをチェックしますか?それが私がここで尋ねていることです。よくわからなければ申し訳ありません...
Jasarien 2009

9
これらのクラスをサブクラス化すると、[super init]がnilを返す可能性がかなり高くなります。すべてのクラスがNSObjectの直接のサブクラスであるとは限りません。nilを返さないという保証はありません。一般的に推奨されているのは、防御的なコーディングの実践にすぎません。
チャック、

7
NSObject initがどんな場合でもnilを返すことができるとは思いません。メモリが不足している場合、割り当ては失敗しますが、成功した場合、initが失敗することはないと思います。NSObjectには、クラス以外のインスタンス変数さえありません。GNUStepでは、「return self」として実装され、Macでの逆アセンブルも同じように見えます。もちろん、これはすべて無関係です。標準のイディオムに従うだけで、それが可能かどうかを心配する必要はありません。
ピーターNルイス

9
私はベストプラクティスに従わないことに固執していません。ただし、そもそもなぜそれらがベストプラクティスであるのかを知りたいと思います。タワーから飛び降りるように言われるようなものです。理由がわからなければ、先に進んでそれを行うだけではありません。底に着く大きなソフト枕はありますか?ジャンプすると知っていたら。そうでなければ、私はしません。私はなぜ私がそれに従っているのかわからないまま、盲目的に練習をしたくありません...
Jasarien

4
allocがnilを返す場合、initはnilに送信されます。これにより、常にnilが生成され、最終的に自分自身がnilになります。
T。

50

この特定のイディオムは、すべてのケースで機能するため、標準です。

珍しいことですが、場合によっては...

[super init];

...別のインスタンスを返すため、自分自身への割り当てが必要です。

そして、それがnilを返す場合があるため、コードが存在しないインスタンス変数スロットを初期化しようとしないようにnilチェックを要求します。

肝心なのは、それが文書化された正しいパターンであり、使用していない場合は間違っているということです。


3
これは、null可能性指定子の観点からまだ当てはまりますか?スーパークラスのイニシャライザがnull以外の場合、チェックする余分なクラッタの価値がありますか?(ただし、NSObject自体にはその-init不満のために何もないようです...)
natevw

[super init]直接のスーパークラスがnilのときにnilを返すケースはありますNSObjectか?これは「すべてが壊れている」のではないか。
Dan Rosenstark

1
@DanRosenstark NSObject直接のスーパークラスではない場合。しかし... NSObject直接のスーパークラスとして宣言したとしても、NSObjectの実装initが実際に呼び出されるものではないように、実行時に何かが変更されている可能性があります。
bbum

1
@bbumに感謝します。これは、バグ修正の方向付けに本当に役立ちました。いくつかのことを除外するのは良いことです!
Dan Rosenstark

26

ほとんどのクラスでは、[super init]からの戻り値がnilであり、標準的な方法で推奨されているようにそれをチェックし、nilの場合は途中で戻ると、基本的にアプリはまだ正常に動作しません。考えてみれば、(self!= nil)チェックがあったとしても、クラスが適切に動作するためには、実際に 99.99%の時間でselfが非nilである必要があります。さて、何らかの理由で[super init] nilを返したとしましょう。基本的にnilに対するチェックは、基本的にはクラスの呼び出し元に見返りを渡しています。成功した。

基本的に、私が得ているのは、99.99%の確率で、if(self!= nil)が堅牢性の観点から何も購入しないということです。これを確実に確実に処理するには、実際には呼び出し階層全体にチェックを入れる必要があります。そしてそれでも、あなたが買うだろう唯一のものはあなたのアプリがもう少しきれいに/堅牢に失敗するということです。しかし、それでも失敗します。

ライブラリクラスが[super init]の結果としてnilを返すことを恣意的に決定した場合は、とにかくかなりf *** edであり、それはライブラリクラスの作成者が実装の誤りを犯したことを示しています。

アプリがはるかに限られたメモリで実行された場合、これはレガシーコーディングの提案のほうが多いと思います。

ただし、Cレベルのコードの場合は、通常、malloc()の戻り値をNULLポインターに対してチェックします。一方、Objective-Cの場合、反対の証拠が見つかるまで、if(self!= nil)チェックは通常はスキップします。なぜ矛盾があるのですか?

Cレベルとmallocレベルでは、一部のケースでは実際に部分的に回復できるためです。Objective-Cでは99.99%のケースでは、[super init]がnilを返すと思うのですが、処理しようとしても、基本的にf *** edです。アプリをクラッシュさせて余波に対処することもできます。


6
よく話されました。同感です。
Roger CS Wernersson

3
+1完全に同意します。ちょっとマイナーなメモ:このパターンが、割り当てがより頻繁に失敗したときの結果だとは思いません。通常、割り当てはinitが呼び出された時点ですでに行われています。allocが失敗した場合、initは呼び出されません。
Nikolai Ruhe

8

これは、上記のコメントの要約のようなものです。

スーパークラスがを返すとしましょうnil。どうなるの?

慣習に従わない場合

initメソッドの途中でコードがクラッシュします。(init意味のないことをしない限り)

あなたが慣習に従っている場合、スーパークラスがnilを返すかもしれないことを知らない(ほとんどの人はここに帰ります)

あなたのインスタンスはnilであるので、あなたのコードは後で何かの時点でクラッシュするでしょう。または、プログラムがクラッシュすることなく予期せず動作することになります。まあ!よろしいですか?知りません...

規約に従えば、サブクラスがnilを返すことを快く許可します

コードのドキュメント(!)には、「returns ... or nil」と明記する必要があり、残りのコードはこれを処理できるように準備する必要があります。今では理にかなっています。


5
ここで興味深い点は、オプション#1はオプション#2 より明らかに好ましいということです。サブクラスのinitがnilを返すようにしたい真の状況がある場合は、#3が推奨されます。コードのバグが原因で発生する可能性がある場合は、#1を使用してください。オプション#2を使用することは、アプリの爆発を後の時点まで遅らせるだけであり、それにより、エラーのデバッグが非常に困難になったときに仕事ができます。黙って例外をキャッチし、処理せずに続行するようなものです。
Mark Amery 2013

または、
Swiftに

7

通常、クラスがから直接派生している場合、そのNSObject必要はありません。ただし、クラスが他のクラスから派生しているnilかのように、それらのイニシャライザがを返す可能性があり、そうであれば、イニシャライザがそれをキャプチャして正しく動作できるようになるのは、良い習慣です。

そして、はい、記録のために、私はベストプラクティスに従って、すべてのクラスに書き込みNSObjectます。


1
これを念頭に置いて、変数を初期化した後、関数を呼び出す前にnilをチェックすることは良い習慣でしょうか?例Foo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software 2013

古いバージョンのGNUstepのようなエキゾチックなランタイムをカウントする場合(それはを返す)、チェックと割り当てのいずれであっても、から継承しNSObjectてもその結果-initが保証されるわけではありません。NSObjectGSObject
Maxthon Chan

3

あなたは正しい、あなたはしばしば単にを書くことができましたが[super init]、それは何かのサブクラスではうまくいきませんでした。人々は、たった1つの標準的なコード行を記憶し、それをたまにしか必要としない場合でも、常にそれを使用することを好むため、標準が得られますif (self = [super init])。これは、nilが返される可能性とself返される以外のオブジェクトの可能性の両方を取ります。アカウントに。


3

よくある間違いは書くことです

self = [[super alloc] init];

これはスーパークラスのインスタンスを返しますが、これはサブクラスのコンストラクタ/初期化で必要なものではありません。混乱を招く可能性のあるサブクラスメソッドに応答しないオブジェクトが返され、メソッドや識別子が見つからないなどの応答がないという混乱したエラーが生成されます。

self = [super init]; 

スーパークラスに、サブクラスのメンバーを設定する前に最初に初期化するメンバー(変数またはその他のオブジェクト)がある場合に必要です。それ以外の場合、objcランタイムはそれらをすべて0またはnilに初期化します。(メモリのチャンクをまったくクリアせずに割り当てることが多いANSI Cとは異なります

そして、はい、基本クラスの初期化は、メモリ不足エラー、コンポーネントの欠落、リソース取得の失敗などが原因で失敗する可能性があるため、nilのチェックは賢明であり、数ミリ秒未満かかります。


2

これは、初期化が機能したことを確認し、initメソッドがnilを返さなかった場合にifステートメントがtrueを返すため、オブジェクトの作成が正しく機能したことを確認する方法です。initが失敗する可能性があると考えることができるいくつかの理由は、おそらくスーパークラスが認識していないオーバーライドされたinitメソッドまたはそのようなものですが、それほど一般的ではないと思います。しかし、それが起こったとしても、クラッシュが発生したとしても何も起こらないので、常にチェックされます...


それはそうですが、hteyが一緒に呼び出されます、alloc fが失敗するとどうなりますか?
ダニエル

allocが失敗すると、initは、initを呼び出しているクラスのインスタンスではなく、nilに送信されると思います。その場合、何も起こらず、[super init]がnilを返したかどうかをテストするコードも実行されません。
ジャサリエン:2009

2
+ allocでは、メモリ割り当ては常に行われるわけではありません。クラスクラスターの場合を考えます。NSStringは、イニシャライザが呼び出されるまで、使用する特定のサブクラスを認識しません。
bbum 2009

1

OS Xでは、-[NSObject init]メモリ上の理由で失敗する可能性はそれほど高くありません。iOSについても同じことが言えません。

また、nil何らかの理由で返される可能性のあるクラスをサブクラス化する場合は、書くことをお勧めします。


2
iOSとMac OSのどちらでも、メモリを割り当てないため、メモリ上の理由で失敗すること-[NSObject init]ほとんどありません。
Nikolai Ruhe

彼はinitではなくallocを意味していたと思います:)
Thomas Tempelmann 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.