Có, có những lợi ích khi sử dụng instancetype
trong mọi trường hợp áp dụng. Tôi sẽ giải thích chi tiết hơn, nhưng hãy để tôi bắt đầu với tuyên bố táo bạo này: Sử dụng instancetype
bất cứ khi nào nó phù hợp, đó là bất cứ khi nào một lớp trả về một thể hiện của cùng một lớp.
Trên thực tế, đây là những gì Apple nói về chủ đề này:
Trong mã của bạn, thay thế các lần xuất hiện id
dưới dạng giá trị trả về bằng instancetype
khi thích hợp. Đây thường là trường hợp cho init
các phương thức và phương thức nhà máy lớp. Mặc dù trình biên dịch tự động chuyển đổi các phương thức bắt đầu bằng phân bổ, phân phối, khởi động hoặc nâng cấp mới và có kiểu id
trả về instancetype
, nhưng nó không chuyển đổi các phương thức khác. Quy ước Objective-C là viết instancetype
rõ ràng cho tất cả các phương thức.
Dù vậy, chúng ta hãy tiếp tục và giải thích lý do tại sao đó là một ý tưởng tốt.
Đầu tiên, một số định nghĩa:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Đối với một nhà máy đẳng cấp, bạn nên luôn luôn sử dụng instancetype
. Trình biên dịch không tự động chuyển đổi id
sang instancetype
. Đó id
là một đối tượng chung chung. Nhưng nếu bạn làm cho nó một instancetype
trình biên dịch sẽ biết loại đối tượng mà phương thức trả về.
Đây không phải là một vấn đề học tập. Chẳng hạn, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
sẽ tạo ra một lỗi trên Mac OS X ( chỉ ) Nhiều phương thức có tên 'writeData:' được tìm thấy với kết quả không khớp, loại tham số hoặc thuộc tính . Lý do là cả NSFileHandle và NSURLHandle đều cung cấp a writeData:
. Vì [NSFileHandle fileHandleWithStandardOutput]
trả về một id
, trình biên dịch không chắc chắn lớp nào writeData:
đang được gọi.
Bạn cần phải giải quyết vấn đề này bằng cách sử dụng:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
hoặc là:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Tất nhiên, giải pháp tốt hơn là tuyên bố fileHandleWithStandardOutput
là trả lại một instancetype
. Sau đó, diễn viên hoặc nhiệm vụ không cần thiết.
(Lưu ý rằng trên iOS, ví dụ này sẽ không tạo ra lỗi vì chỉ NSFileHandle
cung cấp writeData:
ở đó. Các ví dụ khác tồn tại, chẳng hạn như length
, trả về một CGFloat
từ UILayoutSupport
nhưng NSUInteger
từ một NSString
.)
Lưu ý : Kể từ khi tôi viết bài này, các tiêu đề macOS đã được sửa đổi để trả về NSFileHandle
thay vì một id
.
Đối với người khởi tạo, nó phức tạp hơn. Khi bạn gõ này:
- (id)initWithBar:(NSInteger)bar
Thay vào đó, trình biên dịch sẽ giả vờ bạn gõ cái này thay vào đó:
- (instancetype)initWithBar:(NSInteger)bar
Điều này là cần thiết cho ARC. Điều này được mô tả trong Clang Language Extension Các loại kết quả liên quan . Đây là lý do tại sao mọi người sẽ nói với bạn rằng nó không cần thiết để sử dụng instancetype
, mặc dù tôi cho rằng bạn nên. Phần còn lại của câu trả lời này liên quan đến điều này.
Có ba ưu điểm:
- Rõ ràng. Mã của bạn đang làm những gì nó nói, chứ không phải là một cái gì đó khác.
- Mẫu. Bạn đang xây dựng những thói quen tốt cho những lần nó không thành vấn đề.
- Tính nhất quán. Bạn đã thiết lập một số tính nhất quán cho mã của bạn, làm cho nó dễ đọc hơn.
Rõ ràng
Đúng là không có lợi ích kỹ thuật để trở về instancetype
từ một init
. Nhưng điều này là do trình biên dịch tự động chuyển đổi id
thành instancetype
. Bạn đang dựa vào điều này; trong khi bạn đang viết rằng init
trả về một id
, trình biên dịch sẽ diễn giải nó như thể nó trả về một instancetype
.
Đây là tương đương với trình biên dịch:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Đây không phải là tương đương với đôi mắt của bạn. Tốt nhất, bạn sẽ học cách bỏ qua sự khác biệt và lướt qua nó. Đây không phải là điều bạn nên học cách bỏ qua.
Mẫu
Trong khi không có sự khác biệt với init
các hình thức khác, có là một sự khác biệt ngay sau khi bạn xác định một nhà máy lớp.
Hai cái này không tương đương:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Bạn muốn hình thức thứ hai. Nếu bạn quen gõ instancetype
như kiểu trả về của hàm tạo, bạn sẽ hiểu đúng mỗi lần.
Tính nhất quán
Cuối cùng, hãy tưởng tượng nếu bạn kết hợp tất cả lại với nhau: bạn muốn một init
hàm và cũng là một nhà máy lớp.
Nếu bạn sử dụng id
cho init
, bạn kết thúc với mã như thế này:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Nhưng nếu bạn sử dụng instancetype
, bạn sẽ nhận được điều này:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Nó phù hợp hơn và dễ đọc hơn. Họ trả lại điều tương tự, và bây giờ điều đó là hiển nhiên.
Phần kết luận
Trừ khi bạn cố tình viết mã cho trình biên dịch cũ, bạn nên sử dụng instancetype
khi thích hợp.
Bạn nên do dự trước khi viết một tin nhắn trả về id
. Hãy tự hỏi: Đây có phải là một ví dụ của lớp này? Nếu vậy, nó là một instancetype
.
Chắc chắn có những trường hợp bạn cần quay lại id
, nhưng có lẽ bạn sẽ sử dụng instancetype
thường xuyên hơn nhiều.