Apakah bermanfaat untuk mulai menggunakan instancetype daripada id?

226

Dentang menambahkan kata kunci instancetypeyang, sejauh yang saya bisa lihat, menggantikan idsebagai jenis kembali -allocdan init.

Apakah ada manfaat menggunakan instancetypebukan id?

griotspeak
sumber
10
Tidak .. tidak untuk alokasi dan init, karena mereka sudah bekerja seperti ini. Inti dari instancetype adalah agar Anda dapat memberikan alokasi / init metode kustom seperti behavoir.
hooleyhoop
@hooleyhoop mereka tidak bekerja seperti ini. Mereka mengembalikan id. id adalah 'objek Obj-C'. Itu semuanya. Kompiler tidak tahu apa-apa tentang nilai balik selain itu.
griotspeak
5
mereka memang bekerja seperti ini, kontrak dan pengecekan tipe sudah terjadi untuk init, hanya untuk custructor yang Anda butuhkan ini. nshipster.com/instancetype
kocodude
Semuanya sudah maju sedikit, ya. Jenis hasil terkait relatif baru dan perubahan lainnya berarti bahwa ini akan menjadi masalah yang jauh lebih jelas segera.
griotspeak
1
Saya hanya ingin berkomentar bahwa sekarang di iOS 8 banyak metode yang digunakan untuk kembali idtelah diganti instancetype, bahkan initdari NSObject. Jika Anda ingin membuat kode Anda kompatibel dengan swift, Anda harus menggunakaninstancetype
Hola Soy Edu Feliz Navidad

Jawaban:

192

Pasti ada manfaatnya. Saat Anda menggunakan 'id', pada dasarnya Anda tidak mendapatkan pengecekan tipe sama sekali. Dengan instancetype, kompiler dan IDE tahu apa jenis yang dikembalikan, dan dapat memeriksa kode Anda lebih baik dan autocomplete lebih baik.

Hanya gunakan di tempat yang masuk akal saja (yaitu metode yang mengembalikan instance kelas itu); id masih berguna.

Catfish_Man
sumber
8
alloc,, initdll. secara otomatis dipromosikan instancetypeoleh kompiler. Itu tidak berarti tidak ada manfaatnya; memang ada, tapi bukan ini.
Steven Fisher
10
ini berguna untuk konstruktor kenyamanan sebagian besar
Catfish_Man
5
Itu diperkenalkan dengan (dan untuk membantu mendukung) ARC, dengan rilis Lion pada 2011. Satu hal yang menyulitkan adopsi luas dalam Kakao adalah bahwa semua metode kandidat harus diaudit untuk melihat apakah mereka melakukan [alokasi sendiri] daripada [NameOfClass mengalokasikan], karena itu akan sangat membingungkan untuk melakukan [SomeSubClass convenienceConstructor], dengan + convenienceConstructor dinyatakan untuk mengembalikan instancetype, dan tidak mengembalikan instance SomeSubClass.
Catfish_Man
2
'id' hanya dikonversi ke tipe instancet ketika kompilator dapat menyimpulkan keluarga metode. Ini tentu saja tidak melakukannya untuk konstruktor kenyamanan, atau metode pengembalian id lainnya.
Catfish_Man
4
Perhatikan bahwa di iOS 7, banyak metode di Foundation telah dikonversi ke tipe instancet. Saya pribadi melihat tangkapan ini setidaknya 3 kasus kode yang sudah ada sebelumnya salah.
Catfish_Man
336

Ya, ada manfaat menggunakan instancetypedalam semua kasus yang berlaku. Saya akan menjelaskan lebih detail, tetapi saya akan mulai dengan pernyataan tebal ini: Gunakaninstancetype kapan saja sesuai, yang mana setiap kali kelas mengembalikan instance dari kelas yang sama.

Faktanya, inilah yang dikatakan Apple tentang masalah ini:

Dalam kode Anda, ganti kejadian idsebagai nilai balik dengan yang instancetypesesuai. Ini biasanya berlaku untuk initmetode dan metode pabrik kelas. Meskipun kompiler secara otomatis mengonversi metode yang dimulai dengan "alokasi," "init," atau "baru" dan memiliki tipe idpengembalian untuk kembali instancetype, itu tidak mengkonversi metode lain. Konvensi Objective-C adalah menulis instancetypesecara eksplisit untuk semua metode.

Dengan itu, mari kita lanjutkan dan jelaskan mengapa itu ide yang bagus.

Pertama, beberapa definisi:

 @interface Foo:NSObject
 - (id)initWithBar:(NSInteger)bar; // initializer
 + (id)fooWithBar:(NSInteger)bar;  // class factory
 @end

Untuk pabrik kelas, Anda harus selalu menggunakannya instancetype. Kompiler tidak secara otomatis dikonversi idke instancetype. Itu idadalah objek generik. Tetapi jika Anda menjadikannya instancetypekompiler tahu jenis objek apa metode kembali.

Ini bukan masalah akademis. Misalnya, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]akan menghasilkan kesalahan pada Mac OS X ( hanya ) Beberapa metode bernama 'writeData:' ditemukan dengan hasil yang tidak cocok, tipe parameter atau atribut . Alasannya adalah bahwa NSFileHandle dan NSURLHandle menyediakan a writeData:. Sejak [NSFileHandle fileHandleWithStandardOutput]mengembalikan sebuah id, kompiler tidak yakin kelas apa writeData:yang dipanggil.

Anda perlu mengatasinya, menggunakan:

[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];

atau:

NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];

Tentu saja, solusi yang lebih baik adalah mendeklarasikan fileHandleWithStandardOutputsebagai mengembalikan suatuinstancetype . Maka para pemeran atau tugas tidak perlu.

(Perhatikan bahwa pada iOS, contoh ini tidak akan menghasilkan kesalahan karena hanya NSFileHandlemenyediakan kesalahan di writeData:sana. Contoh lain ada, seperti length, yang mengembalikan a CGFloatdari UILayoutSupporttetapi a NSUIntegerdari NSString.)

Catatan : Sejak saya menulis ini, header macOS telah dimodifikasi untuk mengembalikan NSFileHandlebukan id.

Untuk inisialisasi, ini lebih rumit. Saat Anda mengetik ini:

- (id)initWithBar:(NSInteger)bar

... kompiler akan berpura-pura Anda mengetik ini sebagai gantinya:

- (instancetype)initWithBar:(NSInteger)bar

Ini diperlukan untuk ARC. Ini dijelaskan dalam tipe hasil Terkait Ekstensi Bahasa Dentang . Inilah sebabnya mengapa orang akan mengatakan kepada Anda bahwa itu tidak perlu digunakaninstancetype , meskipun saya berpendapat Anda harus menggunakannya. Sisa dari jawaban ini berkaitan dengan ini.

Ada tiga keuntungan:

  1. Eksplisit. Kode Anda melakukan apa yang dikatakannya, bukan sesuatu yang lain.
  2. Pola. Anda sedang membangun kebiasaan yang baik untuk saat-saat yang penting, yang memang ada.
  3. Konsistensi. Anda telah membuat beberapa konsistensi terhadap kode Anda, yang membuatnya lebih mudah dibaca.

Eksplisit

Memang benar bahwa tidak ada manfaat teknis untuk kembali instancetypedari init. Tetapi ini karena kompiler secara otomatis mengonversi idke instancetype. Anda mengandalkan kekhasan ini; saat Anda menulis bahwa initmengembalikan sebuah id, kompiler menafsirkannya seolah-olah mengembalikan sebuahinstancetype .

Ini sama dengan kompiler:

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

Ini tidak setara dengan mata Anda. Paling-paling, Anda akan belajar untuk mengabaikan perbedaan dan membaca sepintas lalu. Ini bukan sesuatu yang harus Anda pelajari untuk diabaikan.

Pola

Meskipun tidak ada perbedaan dengan initdan metode lain, ada adalah perbedaan segera setelah Anda mendefinisikan sebuah pabrik kelas.

Keduanya tidak setara:

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

Anda menginginkan bentuk kedua. Jika Anda terbiasa mengetikinstancetype sebagai tipe pengembalian konstruktor, Anda akan melakukannya dengan benar setiap waktu.

Konsistensi

Akhirnya, bayangkan jika Anda menggabungkan semuanya: Anda menginginkan initfungsi dan juga pabrik kelas.

Jika Anda menggunakan iduntuk init, Anda berakhir dengan kode seperti ini:

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

Tetapi jika Anda menggunakan instancetype, Anda mendapatkan ini:

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

Ini lebih konsisten dan lebih mudah dibaca. Mereka mengembalikan hal yang sama, dan sekarang sudah jelas.

Kesimpulan

Kecuali Anda sengaja menulis kode untuk kompiler lama, Anda harus menggunakannya instancetypesaat yang tepat.

Anda harus ragu sebelum menulis pesan yang mengembalikan id. Tanyakan kepada diri sendiri: Apakah ini mengembalikan instance kelas ini? Jika demikian, ini adalah instancetype.

Tentu saja ada kasus di mana Anda harus kembali id, tetapi Anda mungkin akan menggunakan instancetypelebih sering.

Steven Fisher
sumber
4
Sudah lama sejak saya bertanya ini dan saya sudah lama mengambil sikap seperti yang Anda kemukakan di sini. Saya membiarkannya pergi di sini karena saya pikir, sayangnya, ini akan dianggap sebagai masalah gaya untuk init dan informasi yang disajikan dalam tanggapan sebelumnya memang menjawab pertanyaan dengan cukup jelas.
griotspeak
6
Untuk menjadi jelas: Saya percaya jawaban oleh Catfish_Man benar hanya dalam kalimat pertama . Jawaban oleh hooleyhoop benar kecuali untuk kalimat pertama . Ini adalah dilema yang luar biasa; Saya ingin memberikan sesuatu yang, dari waktu ke waktu, akan terlihat lebih bermanfaat dan lebih benar daripada keduanya. (Dengan segala hormat terhadap kedua; setelah semua, ini jauh lebih jelas sekarang daripada kembali ketika jawaban mereka ditulis.)
Steven Fisher
1
Seiring waktu saya berharap begitu. Saya tidak bisa memikirkan alasan untuk tidak melakukannya, kecuali Apple merasa penting untuk menjaga kompatibilitas sumber dengan (misalnya) menetapkan NSArray baru ke NSString tanpa gips.
Steven Fisher
4
instancetypevs idbenar-benar bukan keputusan gaya. Perubahan terbaru sekitar instancetypebenar - benar membuat jelas bahwa kita harus menggunakan instancetypedi tempat-tempat seperti di -initmana kita maksudkan 'instance dari kelas saya'
griotspeak
2
Anda bilang ada alasan untuk menggunakan id, bisakah Anda menjelaskan? Hanya dua yang bisa saya pikirkan adalah singkatnya dan kompatibilitas; mengingat keduanya dengan mengorbankan mengekspresikan niat Anda, saya pikir itu adalah argumen yang sangat buruk.
Steven Fisher
10

Jawaban di atas lebih dari cukup untuk menjelaskan pertanyaan ini. Saya hanya ingin menambahkan contoh bagi pembaca untuk memahaminya dalam hal pengkodean.

Kelas A

@interface ClassA : NSObject

- (id)methodA;
- (instancetype)methodB;

@end

Kelas 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
}
Gerbang Evol
sumber
ini hanya akan menghasilkan kesalahan kompiler jika Anda tidak menggunakan #import "ClassB.h". jika tidak, kompiler akan menganggap bahwa Anda tahu apa yang Anda lakukan. misalnya, kompilasi berikut, tetapi lumpuh saat runtime: id myNumber = @ (1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADictionary = [myNumber objectForKey: @ "key"];
OlDor
1

Anda juga bisa mendapatkan detail di The Designated Initializer

**

INSTANCETYPE

** Kata kunci ini hanya dapat digunakan untuk jenis kembali, yang cocok dengan jenis penerima kembali. Metode init selalu dinyatakan untuk mengembalikan tipe instancet. Mengapa tidak membuat Pesta jenis pengembalian untuk pesta misalnya, misalnya? Itu akan menimbulkan masalah jika kelas Partai pernah subkelas. Subclass akan mewarisi semua metode dari Party, termasuk initializer dan tipe pengembaliannya. Jika turunan dari subclass dikirimi pesan penginisialisasi ini, apakah itu akan dikembalikan? Bukan pointer ke instance Party, tapi pointer ke instance dari subclass. Anda mungkin berpikir itu Tidak masalah, saya akan mengganti initializer di subclass untuk mengubah tipe return. Tetapi dalam Objective-C, Anda tidak dapat memiliki dua metode dengan pemilih yang sama dan jenis pengembalian yang berbeda (atau argumen). Dengan menetapkan bahwa metode inisialisasi kembali "

Indo

** Sebelum instancetype diperkenalkan di Objective-C, initializers mengembalikan id (eye-dee). Tipe ini didefinisikan sebagai "penunjuk ke objek apa pun". (id sangat mirip void * dalam C.) Pada tulisan ini, templat kelas XCode masih menggunakan id sebagai jenis pengembalian inisialisasi yang ditambahkan dalam kode boilerplate. Tidak seperti tipe instancetype, id dapat digunakan lebih dari sekedar tipe pengembalian. Anda bisa mendeklarasikan variabel atau parameter metode id tipe ketika Anda tidak yakin jenis objek apa yang akan ditunjukkan oleh variabel. Anda dapat menggunakan id saat menggunakan enumerasi cepat untuk beralih di atas larik objek yang banyak atau tidak diketahui. Perhatikan bahwa karena id tidak didefinisikan sebagai "penunjuk ke objek apa pun," Anda tidak menyertakan * ketika mendeklarasikan variabel atau parameter objek jenis ini.

Nam N. HUYNH
sumber