Tak, korzystanie z nich jest korzystne instancetype
we wszystkich przypadkach, w których ma to zastosowanie. Wyjaśnię bardziej szczegółowo, ale zacznę od tego odważnego stwierdzenia: używaj, instancetype
gdy jest to właściwe, czyli za każdym razem, gdy klasa zwraca instancję tej samej klasy.
Oto, co Apple teraz mówi na ten temat:
W swoim kodzie zamień wystąpienia id
jako wartość zwracaną na instancetype
odpowiednią. Zazwyczaj dzieje się tak w przypadku init
metod i metod fabrycznych klas. Mimo że kompilator automatycznie konwertuje metody zaczynające się od „przydzielania”, „inicjowania” lub „nowego” i ma typ id
zwracany do zwrócenia instancetype
, nie konwertuje innych metod. Konwencja celu C polega na instancetype
jawnym pisaniu dla wszystkich metod.
Aby temu zapobiec, przejdźmy dalej i wyjaśnij, dlaczego to dobry pomysł.
Po pierwsze, niektóre definicje:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
W fabryce klasowej zawsze powinieneś używać instancetype
. Kompilator nie automatycznie konwertować id
do instancetype
. To id
jest ogólny obiekt. Ale jeśli to zrobiszinstancetype
kompilator wie, jaki typ obiektu zwraca metoda.
To nie jest problem akademicki. Na przykład [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
wygeneruje błąd w systemie Mac OS X ( tylko ) Znaleziono wiele metod o nazwie „writeData:” z niedopasowanym wynikiem, typem parametru lub atrybutami . Powodem jest to, że zarówno NSFileHandle, jak i NSURLHandle zapewniają writeData:
. Ponieważ [NSFileHandle fileHandleWithStandardOutput]
zwraca an id
, kompilator nie jest pewien, jakiej klasywriteData:
jest wywoływana.
Musisz obejść ten problem, używając:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
lub:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Oczywiście lepszym rozwiązaniem jest zadeklarowanie fileHandleWithStandardOutput
zwrotuinstancetype
. Wtedy obsada lub zadanie nie jest konieczne.
(Należy pamiętać, że na iOS, w tym przykładzie nie będzie produkować błąd jak tylko NSFileHandle
dostarcza writeData:
tam. Istnieją również inne przykłady, takie jak length
, która zwraca CGFloat
z UILayoutSupport
ale NSUInteger
z NSString
).
Uwaga : odkąd to napisałem, nagłówki macOS zostały zmodyfikowane, aby zwracały NSFileHandle
zamiastid
.
W przypadku inicjatorów jest to bardziej skomplikowane. Po wpisaniu:
- (id)initWithBar:(NSInteger)bar
… Kompilator udaje, że zamiast tego wpisałeś:
- (instancetype)initWithBar:(NSInteger)bar
Było to konieczne dla ARC. Jest to opisane w Powiązane typy rozszerzeń języka Clang . Dlatego ludzie powiedzą ci, że nie musisz używaćinstancetype
, chociaż uważam, że powinieneś. Reszta tej odpowiedzi dotyczy tego.
Istnieją trzy zalety:
- Wyraźny. Twój kod robi to, co mówi, a nie coś innego.
- Wzór. Budujesz dobre nawyki na czasy, które mają znaczenie, które istnieją.
- Konsystencja. Ustaliłeś pewną spójność kodu, dzięki czemu jest on bardziej czytelny.
Wyraźny
To prawda, że powrót z nie ma żadnych technicznych korzyści . Ale to dlatego, że kompilator automatycznie konwertuje TO . Polegasz na tym dziwactwie; podczas gdy piszesz, że zwraca an , kompilator interpretuje to tak, jakby zwraca aninstancetype
init
id
instancetype
init
id
instancetype
.
Są one równoważne z kompilatorem:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
To nie jest równoznaczne z twoimi oczami. W najlepszym przypadku nauczysz się ignorować różnicę i przejrzeć ją. To nie jest coś, co powinieneś nauczyć się ignorować.
Wzór
Chociaż nie ma różnicy z init
i innych metod, nie ma różnicy jak najszybciej określić fabrykę klasy.
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 typ zwracany przez konstruktor, za każdym razem będziesz to robić poprawnie.
Konsystencja
Wreszcie, wyobraź sobie, że połączysz to wszystko: potrzebujesz init
funkcji, a także fabryki klasy.
Jeśli używasz id
do init
, możesz skończyć z kodem jak poniżej:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Ale jeśli użyjesz instancetype
, otrzymasz to:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Jest bardziej spójny i bardziej czytelny. Zwracają to samo, a teraz to oczywiste.
Wniosek
O ile celowo nie piszesz kodu dla starych kompilatorów, powinieneś go używać w instancetype
razie potrzeby.
Przed napisaniem zwrotnej wiadomości powinieneś się wahać id
. Zadaj sobie pytanie: czy to zwraca instancję tej klasy? Jeśli tak, to jest instancetype
.
Z pewnością są przypadki, w których musisz wrócić id
, ale prawdopodobnie będziesz używać instancetype
znacznie częściej.