Czy byłoby korzystne zacząć używać instancetype zamiast id?

Clang dodaje słowo kluczowe instancetype to, o ile widzę, zastępuje id jako typ powrotu w -alloc i init.

Czy jest korzyść z używania instancetype zamiast id?

Author: rightfold, 2012-01-23

4 answers

Na pewno jest korzyść. Kiedy używasz 'id', nie masz praktycznie żadnego sprawdzania typu. Dzięki instancetype kompilator i IDE wiedzą, jaki typ rzeczy jest zwracany, i mogą lepiej sprawdzić kod i lepiej autouzupełniać.

Używaj go tylko tam, gdzie ma to oczywiście sens (tj. metoda zwracająca instancję tej klasy); id jest nadal użyteczne.

 186
Author: Catfish_Man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-23 18:47:01

Tak, istnieją korzyści z używania instancetype we wszystkich przypadkach, w których ma to zastosowanie. Wyjaśnię to bardziej szczegółowo, ale zacznę od tego odważnego stwierdzenia: używaj instancetype, gdy jest to właściwe, czyli gdy Klasa zwraca instancję tej samej klasy.

W rzeczywistości, oto co Apple teraz mówi na ten temat:]}

W kodzie zastąp wystąpienia id jako wartość zwracaną przez instancetype, jeśli jest to właściwe. Zazwyczaj tak jest w przypadku metod init i klasy factory metody. Mimo że kompilator automatycznie konwertuje metody, które zaczynają się od "alloc", "init" lub " new " i mają typ zwracania id na return instancetype, nie konwertuje innych metod. Konwencja Objective-C jest zapisem instancetype jawnie dla wszystkich metod.

Z tym na uboczu, przejdźmy dalej i wyjaśnijmy, dlaczego to dobry pomysł.

Po pierwsze, niektóre definicje:

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

Dla fabryki klas, powinieneś zawsze używać instancetype. Kompilator nie konwertuje automatycznie id na instancetype. Że {[11] } jest obiektem ogólnym. Ale jeśli uczynisz go instancetype kompilator wie, jakiego typu obiekt zwraca metoda.

To jestNie problem akademicki. Na przykład, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData] wygeneruje błąd tylko w systemie Mac OS X () znaleziono wiele metod o nazwie 'writeData:' z niedopasowanym wynikiem, typem parametru lub atrybuty . Powodem jest to, że zarówno NSFileHandle, jak i NSURLHandle zapewniają writeData:. Ponieważ [NSFileHandle fileHandleWithStandardOutput] zwraca id, kompilator nie jest pewien, która klasa writeData: jest wywoływana.

Musisz obejść to, używając:

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

Lub:

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

Oczywiście lepszym rozwiązaniem jest zadeklarowanie fileHandleWithStandardOutput jako zwracającego instancetype. Wtedy Obsada lub zadanie nie jest konieczne.

W przypadku systemu iOS, ten przykład nie spowoduje błędu jako jedyny.]} dostarcza writeData: tam. Istnieją inne przykłady, takie jak length, które zwraca CGFloat Z UILayoutSupport, ale NSUInteger z NSString.)

Uwaga: Od kiedy to napisałem, nagłówki macOS zostały zmodyfikowane tak, aby zwracały NSFileHandle zamiast id.

W przypadku inicjalizatorów jest to bardziej skomplikowane. Po wpisaniu tego:

- (id)initWithBar:(NSInteger)bar

...kompilator będzie udawał, że wpisałeś to zamiast:

- (instancetype)initWithBar:(NSInteger)bar
To było konieczne dla ARC. Jest to opisane w języku Clang Rozszerzenia powiązane typy wyników . Dlatego ludzie powiedzą ci, że nie trzeba używać instancetype, chociaż uważam, że powinieneś. Reszta odpowiedzi dotyczy tego.

Są trzy zalety:

  1. Twój kod robi to, co mówi, a nie coś innego.
  2. wzór. budujesz dobre nawyki na czasy, które mają znaczenie, które istnieją.
  3. spójność. ustaliłeś jakieś spójność z Twoim kodem, co czyni go bardziej czytelnym.

Explicit

To prawda, że nie matechnicznych korzyści z powrotu instancetype z init. Jest to spowodowane tym, że kompilator automatycznie konwertuje id na instancetype. Polegasz na tym dziwactwie; kiedy piszesz, że init zwraca id, kompilator interpretuje to tak, jakby zwracał instancetype.

Są to równoważne do kompilator:

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

Te nie są równoważne Twoim oczom. W najlepszym razie nauczysz się ignorować różnicę i przejrzeć ją. to nie jest coś, czego powinieneś nauczyć się ignorować.

Wzór

Podczas gdy nie ma różnicy z init i innymi metodami, istnieje jest różnica, gdy tylko zdefiniujesz fabrykę klas.

Te dwa nie są równoważne:

+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Chcesz drugiej formy. Jeśli jesteś przyzwyczajony do pisania instancetype jako return type of a constructor, you ' ll get it right every time.

Spójność

Wreszcie, wyobraź sobie, że poskładasz to wszystko do kupy: chcesz init funkcji, a także fabryki klas.

Jeśli używasz id do init, kończysz z kodem takim jak:

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

Ale jeśli użyjesz instancetype, dostaniesz to:

- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Jest bardziej spójny i czytelny. Zwracają to samo, a teraz to oczywiste.

Podsumowanie

Chyba, że jesteś celowo pisząc kod dla starych kompilatorów, powinieneś użyć instancetype, gdy jest to właściwe.

Powinieneś zawahać się przed napisaniem wiadomości, która zwróci id. Zadaj sobie pytanie: czy to zwrócenie jest instancją tej klasy? Jeśli tak, to jest to instancetype.

Z pewnością są przypadki, w których musisz zwrócić id, ale prawdopodobnie będziesz używać instancetype znacznie częściej.

 328
Author: Steven Fisher,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-25 18:30:48

Powyższe odpowiedzi są więcej niż wystarczające, aby wyjaśnić to pytanie. Chciałbym tylko dodać przykład dla czytelników, aby zrozumieli go w kategoriach kodowania.

ClassA

@interface ClassA : NSObject

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

@end

Klasa 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
}
 9
Author: Evol Gate,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-18 18:33:36

Możesz również uzyskać szczegóły w wyznaczonym Inicjalizatorze

**

INSTANCETYPE

** To słowo kluczowe może być używane tylko dla typu return, że pasuje do typu return odbiornika. metoda init zawsze deklarowana jako zwracająca instancetype. Dlaczego na przykład nie uczynić typu Return Party dla instancji party? To sprawiłoby problem, gdyby Klasa partyjna była kiedykolwiek podklasowana. Podklasa dziedziczy wszystkie metody ze strony, w tym inicjalizator i jego powrót Typ. Jeśli instancja podklasy została wysłana tym komunikatem inicjującym, to będzie to return? Nie wskaźnik do instancji Party, ale wskaźnik do instancji podklasy. Możesz pomyśleć, że to nie problem, nadpiszę inicjalizator w podklasie, aby zmienić typ powrotu. Ale w Objective-C nie można mieć dwóch metod z tym samym selektorem i różnymi typami zwrotów (lub argumentami). Określając, że metoda inicjalizacji zwraca "instancję obiektu odbierającego", można nigdy nie musisz się martwić, co dzieje się w tej sytuacji. **

ID

** Przed wprowadzeniem typu instancetype w Objective-C, inicjalizatory zwracają id (eye-dee). Ten typ jest zdefiniowany jako "wskaźnik do dowolnego obiektu". (id jest bardzo podobne do void * W C.) W chwili pisania tego tekstu, szablony klas Xcode nadal używają id jako zwracanego typu inicjalizatorów dodanych w kodzie boilerplate. W przeciwieństwie do instancetype, id może być używane jako coś więcej niż tylko Typ zwrotu. Możesz zadeklarować zmienne lub parametry metody wpisz id, gdy nie masz pewności, do jakiego typu obiektu będzie wskazywana zmienna. Można użyć id, gdy używa się szybkiego wyliczenia do iteracji na tablicy wielu lub nieznanych typów obiektów. Zauważ, że ponieważ id jest niezdefiniowany jako "wskaźnik do dowolnego obiektu", nie uwzględniasz * deklarując zmienną lub parametr obiektu tego typu.

 1
Author: Nam N. HUYNH,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-23 08:23:32