Clangは、instancetype
私が見る限りではid
、-alloc
およびの戻り型として置換されるキーワードを追加しますinit
。
のinstancetype
代わりに使用する利点はありid
ますか?
id
ていた多くのメソッドがinstancetype
、init
からでも置き換えられている ことをコメントしたいと思いNSObject
ます。コードをSwiftと互換性を持たせたい場合は、使用する必要がありますinstancetype
Clangは、instancetype
私が見る限りではid
、-alloc
およびの戻り型として置換されるキーワードを追加しますinit
。
のinstancetype
代わりに使用する利点はありid
ますか?
id
ていた多くのメソッドがinstancetype
、init
からでも置き換えられている ことをコメントしたいと思いNSObject
ます。コードをSwiftと互換性を持たせたい場合は、使用する必要がありますinstancetype
回答:
確かにメリットがあります。'id'を使用すると、基本的に型チェックはまったく行われません。instancetypeを使用すると、コンパイラとIDEは返されるもののタイプを認識し、コードをより適切にチェックし、オートコンプリートをより適切に実行できます。
もちろん、意味のある場所でのみ使用してください(つまり、そのクラスのインスタンスを返すメソッド)。idはまだ便利です。
alloc
、init
などはinstancetype
コンパイラによって自動的に昇格されます。それは利点がないと言っているのではありません。ありますが、これではありません。
はい、instancetype
それが適用されるすべてのケースで使用する利点があります。詳細を説明しますが、この大胆なステートメントから始めましょう。instancetype
適切なときはいつでも、つまり、クラスが同じクラスのインスタンスを返すときはいつでも使用します。
実際、これはAppleがこの件に関して今言っていることです:
コード内で
id
、戻り値としてのの出現instancetype
箇所を適切な場所に置き換えます。これは通常、init
メソッドおよびクラスファクトリメソッドの場合です。コンパイラは、「alloc」、「init」、または「new」で始まり、戻り値の型がid
returninstancetype
であるメソッドを自動的に変換しますが、他のメソッドは変換しません。Objective-Cの規則はinstancetype
、すべてのメソッドに対して明示的に記述することです。
それが邪魔にならないので、次に進み、なぜそれが良いアイデアであるかを説明しましょう。
まず、いくつかの定義:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
クラスファクトリの場合は、常にを使用する必要がありますinstancetype
。コンパイラーは自動的にに変換id
しませんinstancetype
。それid
は一般的なオブジェクトです。ただし、これを作成するとinstancetype
、コンパイラはメソッドが返すオブジェクトのタイプを認識します。
これは学術的な問題ではありません。たとえば、[[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
Mac OS X(のみ)でエラーが生成されます。「writeData:」という名前の複数のメソッドが見つかり、結果、パラメータータイプ、または属性が一致しません。その理由は、NSFileHandleとNSURLHandleの両方がを提供するためwriteData:
です。[NSFileHandle fileHandleWithStandardOutput]
はを返すのでid
、コンパイラはwriteData:
呼び出されているクラスを特定できません。
次のいずれかを使用して、これを回避する必要があります。
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
または:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
もちろん、より良い解決策は、fileHandleWithStandardOutput
を返すものとして宣言することinstancetype
です。その後、キャストや割り当ては必要ありません。
(専用としてiOSで、この例では、エラーを生成しないことに注意してくださいNSFileHandle
提供するwriteData:
が。他の例のような、存在length
返し、CGFloat
よりUILayoutSupport
しかしNSUInteger
よりNSString
。)
注:これを書いてから、macOSヘッダーがのNSFileHandle
代わりにを返すように変更されましたid
。
イニシャライザについては、より複雑です。これを入力すると:
- (id)initWithBar:(NSInteger)bar
…コンパイラは、これを代わりに入力したふりをします。
- (instancetype)initWithBar:(NSInteger)bar
これはARCに必要でした。これについては、Clang言語拡張機能に関連する結果タイプで説明しています。このためinstancetype
、を使用する必要がないと人々が言うでしょうが、私はあなたがそうすべきだと主張します。この回答の残りの部分ではこれを扱います。
3つの利点があります。
それは何もありませんというのは本当だ技術を返すの利益instancetype
からinit
。しかし、これはコンパイラが自動的にをに変換するid
ためinstancetype
です。この癖に頼っています。あなたがそれを書いている間init
返すとid
、コンパイラはを返すように解釈しますinstancetype
。
これらはコンパイラと同等です。
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
これらはあなたの目と同等ではありません。せいぜい、あなたは違いを無視してそれをすくうことを学ぶでしょう。これはあなたが無視することを学ぶべきものではありません。
init
他の方法との違いはありませんが、違いは、すぐにあなたがクラスファクトリを定義として。
これら2つは同等ではありません。
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
2番目のフォームが必要です。入力に慣れている場合instancetype
コンストラクターの戻りの型として慣れている場合は、毎回正しくできます。
最後に、すべてを組み合わせると想像してくださいinit
。関数とクラスファクトリが必要です。
id
for を使用するとinit
、次のようなコードになります。
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
しかし、を使用するとinstancetype
、次のようになります。
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
より一貫性があり、読みやすくなっています。彼らは同じことを返しますが、今ではそれは明らかです。
古いコンパイラのコードを意図的に記述しているのでない限りinstancetype
、適切な場合に使用する必要があります。
戻るメッセージを書く前にためらう必要がありますid
。自問してみてください:これはこのクラスのインスタンスを返していますか?もしそうなら、それはinstancetype
です。
返す必要があるケースは確かにありますがid
、おそらくinstancetype
もっと頻繁に使用するでしょう。
instancetype
vs id
はスタイルの決定ではありません。最近の周りの変更により、「クラスのインスタンス」を意味するような場所でinstancetype
使用する必要があることが明確になりましたinstancetype
-init
上記の回答は、この質問を説明するには十分すぎるほどです。読者がコーディングに関して理解できるように、例を追加したいと思います。
ClassA
@interface ClassA : NSObject
- (id)methodA;
- (instancetype)methodB;
@end
クラスB
@interface ClassB : NSObject
- (id)methodX;
@end
TestViewController.m
#import "ClassA.h"
#import "ClassB.h"
- (void)viewDidLoad {
[[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime
[[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}
Designated Initializerでも詳細を確認できます
**
**このキーワードは戻り値のタイプにのみ使用でき、受信者の戻り値のタイプと一致します。initメソッドは常にinstancetypeを返すように宣言されています。たとえば、戻り値のタイプをPartyインスタンスのPartyにしてみませんか?Partyクラスがサブクラス化された場合、問題が発生します。サブクラスは、初期化子とその戻り値の型を含む、Partyからすべてのメソッドを継承します。サブクラスのインスタンスにこの初期化メッセージが送信された場合、それは返されますか?Partyインスタンスへのポインターではなく、サブクラスのインスタンスへのポインター。それは問題ないと思うかもしれませんが、戻り値の型を変更するためにサブクラスの初期化子をオーバーライドします。ただし、Objective-Cでは、同じセレクターと異なる戻り値の型(または引数)を持つ2つのメソッドを使用することはできません。初期化メソッドが「
** instancetypeがObjective-Cに導入される前は、イニシャライザはid(eye-dee)を返します。このタイプは、「任意のオブジェクトへのポインタ」として定義されています。(idはCのvoid *によく似ています。)この記事の執筆時点では、XCodeクラステンプレートは、ボイラープレートコードに追加された初期化子の戻り型としてidを使用しています。instancetypeとは異なり、idは単なる戻り値の型としてだけではなく、それ以外のものとしても使用できます。変数が指すオブジェクトの型がわからない場合は、変数またはid型のメソッドパラメータを宣言できます。高速列挙を使用して複数または不明なタイプのオブジェクトの配列を反復する場合は、idを使用できます。idは「任意のオブジェクトへのポインタ」として定義されていないため、このタイプの変数またはオブジェクトパラメータを宣言するときに*を含めないことに注意してください。