İd yerine instancetype kullanmaya başlamak faydalı olur mu?


226

Clang instancetype, görebildiğim kadarıyla ve idiçinde bir dönüş türü olarak değiştirilen bir anahtar kelime ekler .-allocinit

Kullanmanın bir faydası var mıdır instancetypeyerine id?


10
Hayır. Zaten böyle çalıştıkları için tahsis ve init için değil. İnstancetype noktası, behavoir gibi tahsis / init gibi özel yöntemler verebilmenizdir.
hooleyhoop

@ schooleyhoop böyle çalışmazlar. Kimlik geri dönüyorlar. id 'Obj-C nesnesidir'. Hepsi bu. Derleyici, dönüş değeri hakkında başka bir şey bilmiyor.
Ocak'ta griotspeak

5
Bu şekilde çalışırlar, sözleşme ve tip kontrolü zaten init için gerçekleşiyor, sadece custructors için buna ihtiyacınız var. nshipster.com/instancetype
kocodude

İşler biraz ilerledi, evet. İlgili sonuç türleri nispeten yenidir ve diğer değişiklikler bunun çok yakında daha açık bir konu olacağı anlamına gelir.
Haziran'da griotspeak

1
Sadece açıklama istiyorum artık iOS 8 dönmek için kullanılan yöntemlerden çok üzerinde idolan değiştirilmiştir instancetypebile, initgelen NSObject. Kodunuzu hızlı bir şekilde uyumlu hale getirmek istiyorsanız kullanmanız gerekirinstancetype
Hola Soy Edu Feliz Navidad

Yanıtlar:


192

Kesinlikle bir faydası var. 'İd' kullandığınızda, aslında hiçbir tür denetimi almazsınız. İnstancetype ile, derleyici ve IDE ne tür bir şey döndürüldüğünü bilir ve kodunuzu daha iyi kontrol edebilir ve daha iyi otomatik tamamlayabilir.

Sadece elbette mantıklı olduğu yerlerde kullanın (yani, bu sınıfın bir örneğini döndüren bir yöntem); id hala yararlı.


8
alloc,, initvb instancetype. derleyici tarafından otomatik olarak yükseltilir . Bunun faydası olmadığı anlamına gelmez; var, ama bu değil.
Steven Fisher

10
çoğunlukla
inşaatçılar

5
2011 yılında Lion'un piyasaya sürülmesi ile ARC ile tanıtıldı (ve desteklemeye yardımcı oldu). Kakao'da yaygın olarak benimsenmesini karmaşıklaştıran bir şey, tüm aday yöntemlerin [NameOfClass yerine [kendi kendine ayırma] yapıp yapmadıklarını görmek için denetlenmeleri gerektiğidir. [ayırmak], çünkü [ConveniSubClass convenientConstructor] yapmak çok kafa karıştırıcı olurdu, + convenientConstructor ile instancetype döndürdüğü bildirildi ve bir SomeSubClass örneği döndürmedi.
Catfish_Man

2
'id' yalnızca derleyici yöntem ailesini çıkarabildiğinde instancetype biçimine dönüştürülür. Kesinlikle kolaylık kurucuları veya diğer kimlik döndürme yöntemleri için bunu yapmaz.
Catfish_Man

4
İOS 7'de Foundation'daki birçok yöntemin örnek tipine dönüştürüldüğünü unutmayın. Şahsen bu yakalama yanlış mevcut kod en az 3 vaka gördüm.
Catfish_Man

336

Evet, instancetypegeçerli olduğu her durumda kullanmanın faydaları vardır . Daha ayrıntılı olarak açıklayacağım, ancak şu cesur ifadeyle başlayayım: Uygun instancetypeolduğunda kullanın , yani bir sınıf aynı sınıfın bir örneğini döndürdüğünde.

Aslında, Apple şu anda bu konuda şunları söylüyor:

Kodunuzda, iddönüş değeri olarak oluşumlarını instancetypeuygun olanlarla değiştirin. Bu genellikle inityöntemler ve sınıf fabrikası yöntemleri için geçerlidir. Derleyici otomatik olarak “alloc” “init” veya “yeni” ile başlayan ve bir dönüş türü yöntemleri dönüştürür olsa idgeri dönüşü instancetype, diğer metotların dönüştürmez. Amaç-C kuralı instancetypetüm yöntemler için açıkça yazmaktır .

Yoldan çekilip, bunun neden iyi bir fikir olduğunu açıklayalım.

İlk olarak, bazı tanımlar:

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

Sınıf fabrikası için her zaman kullanmalısınız instancetype. Derleyici otomatik olarak dönüştürmez idiçin instancetype. Bu idgenel bir nesne. Ancak bunu bir instancetypederleyici yaparsanız , yöntemin ne tür bir nesne döndürdüğünü bilir.

Bu akademik bir sorun değil . Örneğin [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData], Mac OS X'te bir hata oluşturur ( yalnızca ) Eşleşmeyen sonuç, parametre türü veya niteliklerle bulunan 'writeData:' adlı birden çok yöntem bulunur . Bunun nedeni hem NSFileHandle hem de NSURLHandle'ın a writeData:. Yana [NSFileHandle fileHandleWithStandardOutput]getiriler bir id, derleyici hangi sınıf kesin değildir writeData:çağrıldığı.

Aşağıdakilerden birini kullanarak bu soruna geçici bir çözüm bulmanız gerekir:

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

veya:

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

Tabii ki, daha iyi bir çözüm fileHandleWithStandardOutputgeri dönen olarak ilan etmektir instancetype. Sonra oyuncu kadrosu veya ödev gerekli değildir.

(Yalnızca iOS'taki, bu örnek, bir hata üretmek olmayacak Not NSFileHandlebir sağlar writeData:vardır. Diğer örnekler, örneğin, mevcut lengthbir döndüren CGFloatgelen UILayoutSupportancak bir NSUIntegermesafede NSString).

Not : Bunu yazdığım için, macOS başlıkları bir NSFileHandleyerine döndürülecek şekilde değiştirildi id.

Başlatıcılar için daha karmaşıktır. Bunu yazdığınızda:

- (id)initWithBar:(NSInteger)bar

… Derleyici bunun yerine yazmış gibi davranacaktır:

- (instancetype)initWithBar:(NSInteger)bar

Bu ARC için gerekliydi. Bu, Clang Dil Uzantıları İlgili sonuç türlerinde açıklanmaktadır . Bu yüzden insanlar sana kullanmanın gerekli olmadığını söyleyecekler instancetype, ama ben bunu yapman gerektiğini savunuyorum. Bu cevabın geri kalanı bununla ilgilidir.

Üç avantajı vardır:

  1. Açık. Kodunuz başka bir şey yerine, söylediklerini yapıyor.
  2. Desen. Önemli olan zamanlarda var olan iyi alışkanlıklar inşa ediyorsunuz.
  3. Tutarlılık. Kodunuzda bir miktar tutarlılık belirlediniz, bu da onu daha okunabilir hale getirdi.

Açık

Bir ülkeden dönmenin teknik bir yararı olmadığı doğrudur . Derleyici otomatik dönüştürür çünkü Ama bu To . Bu tuhaflığa güveniyorsun; bir döndürme yazdığını yazarken , derleyici bunu bir döndürme işlemi olarak yorumluyor .instancetypeinitidinstancetypeinitidinstancetype

Bunlar derleyiciye eşdeğerdir :

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

Bunlar gözlerinizle eşdeğer değil. En iyi ihtimalle, farkı görmezden gelmeyi ve üzerinde kaymayı öğreneceksiniz. Bu görmezden gelmeyi öğrenmen gereken bir şey değil.

Desen

Hiçbir farkı olmasa da initve diğer yöntemler, orada olan bir sınıf fabrika tanımlayan en kısa sürede bir fark.

Bu ikisi eşdeğer değildir:

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

İkinci formu istiyorsunuz. Bir kurucunun instancetypedönüş türü olarak yazmaya alışkınsanız , her seferinde doğru şekilde alırsınız.

Tutarlılık

Son olarak, hepsini bir araya getirdiğinizi düşünün: bir initfonksiyon ve aynı zamanda bir sınıf fabrikası istiyorsunuz .

Eğer kullanırsanız idiçin init, böyle koduyla sonuna kadar:

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

Ancak kullanırsanız instancetype, bunu elde edersiniz:

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

Daha tutarlı ve daha okunabilir. Aynı şeyi geri getiriyorlar ve şimdi bu açık.

Sonuç

Eski derleyiciler için kasıtlı olarak kod yazmadığınız sürece, instancetypeuygun olduğunda kullanmalısınız .

Geri dönen bir mesaj yazmadan önce tereddüt etmelisiniz id. Kendinize sorun: Bu, bu sınıfın bir örneği mi? Eğer öyleyse, bu bir instancetype.

Kesinlikle geri dönmeniz gereken durumlar var id, ancak muhtemelen instancetypeçok daha sık kullanacaksınız .


4
Bunu sorduğumdan bu yana bir süre geçti ve uzun zamandır burada ortaya koyduğunuz gibi bir tutum aldım. Buraya gitmesine izin verdim çünkü maalesef bunun init için bir stil meselesi olarak kabul edileceğini ve önceki yanıtlarda sunulan bilgilerin soruyu oldukça açık bir şekilde yanıtladığını düşünüyorum.
griotspeak

6
Açık olmak gerekirse: Catfish_Man'ın cevabının sadece ilk cümlesinde doğru olduğuna inanıyorum . Hooleyhoop'un cevabı ilk cümlesi dışında doğrudur . Bu bir ikilem cehennemi; Zamanla her ikisinden de daha kullanışlı ve daha doğru görülebilecek bir şey sağlamak istedim. (Bu ikisine tüm saygımla; sonuçta bu, cevaplarının yazıldığı zamandan çok daha açıktır.)
Steven Fisher

1
Zamanla umarım. Apple, örneğin bir döküm olmadan bir NSString'e yeni bir NSArray atama ile kaynak uyumluluğunu korumanın önemli olduğunu düşünmedikçe, yapmamak için herhangi bir neden düşünemiyorum.
Steven Fisher

4
instancetypevs idgerçekten bir stil kararı değil. Çevresindeki son değişiklikler instancetypegerçekten 'sınıfımın bir örneği' demek istediğimiz instancetypeyerlerde kullanmamız gerektiğini açıkça ortaya -init
koyuyor

2
Kimliğin kullanılmasının nedenleri olduğunu söylediniz, ayrıntıya girebilir misiniz? Aklıma gelen sadece ikisi kısalık ve geriye dönük uyumluluk; her ikisinin de niyetinizi ifade etme pahasına olduğunu düşünürsek, bunların gerçekten zayıf argümanlar olduğunu düşünüyorum.
Steven Fisher

10

Yukarıdaki cevaplar bu soruyu açıklamak için fazlasıyla yeterli. Ben sadece kodlama açısından okuyucular için bir örnek eklemek istiyorum.

A sınıfı

@interface ClassA : NSObject

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

@end

Sınıf 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
}

bu sadece #import "ClassB.h" kullanmazsanız derleyici hatası oluşturur. aksi takdirde derleyici ne yaptığınızı bildiğinizi varsayacaktır. örneğin, aşağıdaki derlenir, ancak çalışma zamanında çöküyor: id myNumber = @ (1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADictionary = [myNumber objectForKey: @ "anahtar"];
OlDor

1

Ayrıca Atanmış Başlatıcıda ayrıntılar alabilirsiniz

**

instancetype

** Bu anahtar kelime yalnızca alıcının dönüş türüyle eşleştiği dönüş türü için kullanılabilir. init yöntemi her zaman instancetype döndürdüğünü bildirir. Örneğin, parti için iade türünü neden yapmıyorsunuz? Parti sınıfı hiç alt sınıfa sahip olsaydı bu bir soruna neden olur. Alt sınıf, başlatıcı ve dönüş türü de dahil olmak üzere tüm yöntemleri Taraftan devralır. Alt sınıfın bir örneği bu başlatıcı iletisini gönderirse, bu döndürülecek mi? Bir Parti örneğine işaretçi değil, alt sınıf örneğine işaretçi. Bunun bir sorun olmadığını düşünebilirsiniz, dönüş türünü değiştirmek için alt sınıftaki başlatıcıyı geçersiz kılacağım. Ancak Objective-C'de aynı seçiciye ve farklı dönüş türlerine (veya bağımsız değişkenlere) sahip iki yönteminiz olamaz. Bir başlatma yönteminin "

İD

** Nesnel-C türü, Objective-C'de kullanılmadan önce, başlatıcılar id (eye-dee) değerini döndürür. Bu tür "herhangi bir nesneye işaretçi" olarak tanımlanır. (id, C'deki void * işlevine çok benzer.) Bu yazıdan itibaren, XCode sınıfı şablonları, id kodunu, kaynak plakası koduna eklenen başlatıcıların dönüş türü olarak kullanmaya devam eder. İnstancetype öğesinin aksine, id yalnızca bir dönüş türünden daha fazlası olarak kullanılabilir. Değişkenin ne tür bir nesneyi göstereceğinden emin değilseniz, id türündeki değişkenleri veya yöntem parametrelerini bildirebilirsiniz. Birden fazla veya bilinmeyen nesne dizisi üzerinden yineleme yapmak için hızlı numaralandırma kullanırken id kullanabilirsiniz. İd, "herhangi bir nesneye işaretçi" olarak tanımlanmadığından, bu türde bir değişken veya nesne parametresi bildirirken * eklemediğinizi unutmayın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.