idの代わりにinstancetypeの使用を開始することは有益でしょうか?


226

Clangは、instancetype私が見る限りではid-allocおよびの戻り型として置換されるキーワードを追加しますinit

instancetype代わりに使用する利点はありidますか?


10
いいえ。allocとinitには対応していません。すでにこのように機能しているためです。instancetypeのポイントは、behavoirのようなカスタムメソッドalloc / initを提供できるようにすることです。
hooleyhoop

@hooleyhoop彼らはこのように動作しません。IDを返します。idは「Obj-Cオブジェクト」です。以上です。コンパイラはそれ以外は戻り値について何も知りません。
griotspeak 2013年

5
彼らはこのように機能します。コントラクトと型のチェックは、初期化のために既に行われています。nshipster.com/instancetype
kocodude

はい、物事はかなり進んでいます。関連する結果タイプは比較的新しいものであり、他の変更により、これはすぐにもっと明確な問題になるでしょう。
griotspeak 2013年

1
iOS 8では、以前から返されidていた多くのメソッドがinstancetypeinitからでも置き換えられている ことをコメントしたいと思いNSObjectます。コードをSwiftと互換性を持たせたい場合は、使用する必要がありますinstancetype
Hola Soy Edu Feliz Navidad 14

回答:


192

確かにメリットがあります。'id'を使用すると、基本的に型チェックはまったく行われません。instancetypeを使用すると、コンパイラとIDEは返されるもののタイプを認識し、コードをより適切にチェックし、オートコンプリートをより適切に実行できます。

もちろん、意味のある場所でのみ使用してください(つまり、そのクラスのインスタンスを返すメソッド)。idはまだ便利です。


8
allocinitなどはinstancetypeコンパイラによって自動的に昇格されます。それは利点がないと言っているのではありません。ありますが、これではありません。
スティーブンフィッシャー

10
ほとんどの場合、便利なコンストラクタに役立ちます
Catfish_Man 2013

5
これは、2011年にLionがリリースされたときに、ARCで(およびサポートを支援するために)導入されました。Cocoaで広く採用されることを困難にしている1つの理由は、候補となるすべてのメソッドを監査して、[NameOfClass]ではなく[self alloc]を実行するかどうかを確認する必要があることです。 [alloc]]を使用すると、[SomeSubClassienceConstructor]を実行するのが非常に混乱し、+ convenienceConstructorがinstancetypeを返すように宣言され、SomeSubClassのインスタンスを返さないようになります。
Catfish_Man 2013年

2
'id'は、コンパイラーがメソッドファミリーを推測できる場合にのみ、instancetypeに変換されます。ほとんどの場合、便利なコンストラクタやその他のidを返すメソッドではそうしません。
Catfish_Man 2013年

4
iOS 7では、Foundationの多くのメソッドがinstancetypeに変換されています。私は個人的に、これが不正な既存のコードの少なくとも3つのケースをキャッチするのを見てきました。
Catfish_Man 2013年

336

はい、instancetypeそれが適用されるすべてのケースで使用する利点があります。詳細を説明しますが、この大胆なステートメントから始めましょう。instancetype適切なときはいつでも、つまり、クラスが同じクラスのインスタンスを返すときはいつでも使用します。

実際、これはAppleがこの件に関して今言っていることです:

コード内でid、戻り値としてのの出現instancetype箇所を適切な場所に置き換えます。これは通常、initメソッドおよびクラスファクトリメソッドの場合です。コンパイラは、「alloc」、「init」、または「new」で始まり、戻り値の型がidreturn instancetypeであるメソッドを自動的に変換しますが、他のメソッドは変換しません。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つの利点があります。

  1. 明示的。あなたのコードは、他の何かではなく、それが言うことを実行しています。
  2. パターン。あなたはそれが重要な時のために良い習慣を築いています、それは確かに存在します。
  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。関数とクラスファクトリが必要です。

idfor を使用するとinit、次のようなコードになります。

- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

しかし、を使用するとinstancetype、次のようになります。

- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;

より一貫性があり、読みやすくなっています。彼らは同じことを返しますが、今ではそれは明らかです。

結論

古いコンパイラのコードを意図的に記述しているのでない限りinstancetype、適切な場合に使用する必要があります。

戻るメッセージを書く前にためらう必要がありますid。自問してみてください:これはこのクラスのインスタンスを返していますか?もしそうなら、それはinstancetypeです。

返す必要があるケースは確かにありますがid、おそらくinstancetypeもっと頻繁に使用するでしょう。


4
質問して久しぶりですが、ここに出てきたような立場をとっています。残念ながら、これはinitのスタイルの問題と見なされ、以前の応答で提示された情報が質問にかなり明確に答えたと思うので、ここに移しました。
griotspeak 2013

6
明確に言うと、Catfish_Manの答えは最初の文だけが正しいと思います。hooleyhoopの答えは、最初の文を除いて正しいです。それはジレンマの地獄です。時間の経過とともに、どちらかよりも有用で正確であると思われるものを提供したかったのです。(これらの2つにすべての敬意を払って;結局のところ、これは彼らの答えが書かれたときよりもずっと明白です。)
Steven Fisher

1
時間をかけて私はそう願っています。キャストせずに新しいNSArrayをNSStringに割り当てるなど、ソースの互換性を維持することが重要であるとAppleが感じない限り、私はそうしない理由は考えられません。
Steven Fisher

4
instancetypevs idはスタイルの決定ではありません。最近の周りの変更により、「クラスのインスタンス」を意味するような場所でinstancetype使用する必要があることが明確になりましたinstancetype-init
griotspeak

2
idを使用する理由があるとおっしゃっていましたが、詳しく説明していただけますか?考えられるのは、簡潔さと下位互換性だけです。どちらもあなたの意図を表現することを犠牲にしていることを考えると、それらは本当に悪い議論だと思います。
Steven Fisher

10

上記の回答は、この質問を説明するには十分すぎるほどです。読者がコーディングに関して理解できるように、例を追加したいと思います。

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
}

これは、#import "ClassB.h"を使用しない場合にのみコンパイラエラーを生成します。そうしないと、コンパイラーはユーザーが何をしているかを知っていると想定します。たとえば、次のコードはコンパイルされますが、実行時にクラッシュします。id myNumber = @(1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADictionary = [myNumber objectForKey:@ "key"];
OlDor

1

Designated Initializerでも詳細を確認できます

**

インスタンスタイプ

**このキーワードは戻り値のタイプにのみ使用でき、受信者の戻り値のタイプと一致します。initメソッドは常にinstancetypeを返すように宣言されています。たとえば、戻り値のタイプをPartyインスタンスのPartyにしてみませんか?Partyクラスがサブクラス化された場合、問題が発生します。サブクラスは、初期化子とその戻り値の型を含む、Partyからすべてのメソッドを継承します。サブクラスのインスタンスにこの初期化メッセージが送信された場合、それは返されますか?Partyインスタンスへのポインターではなく、サブクラスのインスタンスへのポインター。それは問題ないと思うかもしれませんが、戻り値の型を変更するためにサブクラスの初期化子をオーバーライドします。ただし、Objective-Cでは、同じセレクターと異なる戻り値の型(または引数)を持つ2つのメソッドを使用することはできません。初期化メソッドが「

ID

** instancetypeがObjective-Cに導入される前は、イニシャライザはid(eye-dee)を返します。このタイプは、「任意のオブジェクトへのポインタ」として定義されています。(idはCのvoid *によく似ています。)この記事の執筆時点では、XCodeクラステンプレートは、ボイラープレートコードに追加された初期化子の戻り型としてidを使用しています。instancetypeとは異なり、idは単なる戻り値の型としてだけではなく、それ以外のものとしても使用できます。変数が指すオブジェクトの型がわからない場合は、変数またはid型のメソッドパラメータを宣言できます。高速列挙を使用して複数または不明なタイプのオブジェクトの配列を反復する場合は、idを使用できます。idは「任意のオブジェクトへのポインタ」として定義されていないため、このタイプの変数またはオブジェクトパラメータを宣言するときに*を含めないことに注意してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.