Sẽ có ích khi bắt đầu sử dụng instancetype thay vì id?


226

Clang thêm một từ khóa instancetypemà theo như tôi có thể thấy, thay thế idnhư một kiểu trả về trong -allocinit.

Có một lợi ích để sử dụng instancetypethay vì id?


10
Không .. không dành cho cấp phát và init, vì chúng đã hoạt động như thế này. Quan điểm của instancetype là để bạn có thể đưa ra các phương thức tùy chỉnh cấp phát / init như hành vi.
hooleyhoop

@hooleyhoop họ không làm việc như thế này. Họ trả lại id. id là 'một đối tượng Obj-C'. Đó là tất cả. Trình biên dịch không biết gì về giá trị trả về khác.
Griotspeak

5
Họ làm việc như thế này, việc kiểm tra hợp đồng và loại đã xảy ra đối với init, chỉ đối với người quản lý bạn mới cần điều này. nshipster.com/instancetype
kocodude

Mọi thứ đã tiến lên khá nhiều, vâng. Các loại kết quả liên quan là tương đối mới và các thay đổi khác có nghĩa là đây sẽ là một vấn đề rõ ràng hơn nhiều sớm.
Griotspeak

1
Tôi chỉ muốn nhận xét rằng bây giờ trên iOS 8, rất nhiều phương thức được sử dụng để trả về idđã được thay thế bằng instancetype, thậm chí inittừ NSObject. Nếu bạn muốn làm cho mã của mình tương thích với swift, bạn phải sử dụnginstancetype
Hola Soy Edu Feliz Navidad

Câu trả lời:


192

Chắc chắn có một lợi ích. Khi bạn sử dụng 'id', về cơ bản bạn không có loại kiểm tra nào cả. Với instancetype, trình biên dịch và IDE biết loại nào đang được trả về và có thể kiểm tra mã của bạn tốt hơn và tự động hoàn thành tốt hơn.

Chỉ sử dụng nó trong trường hợp có ý nghĩa của khóa học (tức là một phương thức trả về một thể hiện của lớp đó); id vẫn hữu ích.


8
alloc, initvv được tự động thăng cấp lên instancetypebởi trình biên dịch. Điều đó không có nghĩa là không có lợi ích; Có, nhưng nó không phải là cái này.
Steven Fisher

10
nó rất hữu ích cho các nhà xây dựng tiện lợi
Catfish_Man

5
Nó được giới thiệu với (và để hỗ trợ) ARC, với việc phát hành Lion vào năm 2011. Một điều làm phức tạp việc áp dụng rộng rãi trong Ca cao là tất cả các phương pháp ứng cử viên phải được kiểm tra để xem liệu họ có [tự cấp phát] thay vì [NameOfClass cấp phát], bởi vì sẽ rất khó hiểu khi thực hiện [someSubClass tiện ích Trình xây dựng], với + tiện ích Trình tạo được khai báo để trả về instancetype và không trả về phiên bản của someSubClass.
Catfish_Man

2
'id' chỉ được chuyển đổi thành instancetype khi trình biên dịch có thể suy ra họ phương thức. Nó chắc chắn không làm như vậy đối với các nhà xây dựng tiện lợi hoặc các phương thức trả lại id khác.
Catfish_Man

4
Lưu ý rằng trong iOS 7, nhiều phương thức trong Foundation đã được chuyển đổi sang instancetype. Cá nhân tôi đã thấy điều này bắt được ít nhất 3 trường hợp mã tồn tại không chính xác.
Catfish_Man

336

Có, có những lợi ích khi sử dụng instancetypetrong 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 instancetypebấ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 iddưới dạng giá trị trả về bằng instancetypekhi thích hợp. Đây thường là trường hợp cho initcá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 idtrả 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 instancetyperõ 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 idsang instancetype. Đó idlà một đối tượng chung chung. Nhưng nếu bạn làm cho nó một instancetypetrì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ố fileHandleWithStandardOutputlà 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ỉ NSFileHandlecung cấp writeData:ở đó. Các ví dụ khác tồn tại, chẳng hạn như length, trả về một CGFloattừ UILayoutSupportnhưng NSUIntegertừ 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ề NSFileHandlethay 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:

  1. 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.
  2. 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 đề.
  3. 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ề instancetypetừ một init. Nhưng điều này là do trình biên dịch tự động chuyển đổi idthành instancetype. Bạn đang dựa vào điều này; trong khi bạn đang viết rằng inittrả 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 initcác hình thức khác, có 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õ instancetypenhư 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 inithàm và cũng là một nhà máy lớp.

Nếu bạn sử dụng idcho 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 instancetypekhi 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 instancetypethường xuyên hơn nhiều.


4
Đã được một thời gian kể từ khi tôi hỏi điều này và từ lâu tôi đã đưa ra lập trường giống như lập trường mà bạn đưa ra ở đây. Tôi để nó ở đây vì tôi nghĩ rằng, thật không may, đây sẽ được coi là một vấn đề về phong cách cho init và thông tin được trình bày trong các phản hồi trước đó đã trả lời câu hỏi khá rõ ràng.
griotspeak

6
Để rõ ràng: Tôi tin rằng câu trả lời của Catfish_Man chỉ đúng trong câu đầu tiên . Câu trả lời của hooleyhoop là chính xác ngoại trừ câu đầu tiên . Đó là một địa ngục của một vấn đề nan giải; Tôi muốn cung cấp một cái gì đó, theo thời gian, sẽ được coi là hữu ích hơn và chính xác hơn cả. (Với tất cả sự tôn trọng dành cho hai người đó, sau tất cả, điều này rõ ràng hơn nhiều so với khi câu trả lời của họ được viết.)
Steven Fisher

1
Theo thời gian tôi hy vọng như vậy. Tôi không thể nghĩ ra bất kỳ lý do nào để không, trừ khi Apple cảm thấy điều quan trọng là duy trì khả năng tương thích nguồn với (ví dụ) gán NSArray mới cho NSString mà không cần truyền.
Steven Fisher

4
instancetypevs idthực sự không phải là một quyết định phong cách. Những thay đổi gần đây instancetypethực sự cho thấy rõ rằng chúng ta nên sử dụng instancetypeở những nơi như -initý nghĩa của chúng tôi 'một ví dụ của lớp tôi'
griotspeak

2
Bạn nói có lý do để sử dụng id, bạn có thể giải thích? Hai thứ duy nhất tôi có thể nghĩ đến là khả năng tương thích ngắn gọn và ngược; xem xét cả hai đều phải trả giá cho việc thể hiện ý định của bạn, tôi nghĩ đó là những lý lẽ thực sự kém.
Steven Fisher

10

Câu trả lời trên là quá đủ để giải thích câu hỏi này. Tôi chỉ muốn thêm một ví dụ để người đọc hiểu nó về mặt mã hóa.

Lớp học

@interface ClassA : NSObject

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

@end

Lớp B

@interface ClassB : NSObject

- (id)methodX;

@end

TestViewControll.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
}

điều này sẽ chỉ tạo ra lỗi trình biên dịch nếu bạn không sử dụng #import "ClassB.h". nếu không, trình biên dịch sẽ cho rằng bạn biết bạn đang làm gì. ví dụ: các biên dịch sau, nhưng gặp sự cố khi chạy: id myNumber = @ (1); id iAssumedThatWasAnArray = myNumber [0]; id iAssumedThatWasADipedia = [myNumber objectForKey: @ "key"];
OlDor

1

Bạn cũng có thể nhận thông tin chi tiết tại Công cụ khởi tạo được chỉ định

**

INSTANCETYPE

** Từ khóa này chỉ có thể được sử dụng cho loại trả về, phù hợp với loại trả về. phương thức init luôn được khai báo để trả về instancetype. Tại sao không tạo ra kiểu trả về Đảng chẳng hạn? Điều đó sẽ gây ra vấn đề nếu lớp Đảng đã từng được phân lớp. Lớp con sẽ kế thừa tất cả các phương thức từ Party, bao gồm trình khởi tạo và kiểu trả về của nó. Nếu một thể hiện của lớp con được gửi thông điệp khởi tạo này, đó sẽ là trả về? Không phải là một con trỏ tới một thể hiện của Đảng, mà là một con trỏ tới một thể hiện của lớp con. Bạn có thể nghĩ rằng không có vấn đề gì, tôi sẽ ghi đè trình khởi tạo trong lớp con để thay đổi kiểu trả về. Nhưng trong Objective-C, bạn không thể có hai phương thức có cùng bộ chọn và các kiểu trả về (hoặc đối số) khác nhau. Bằng cách chỉ định rằng một phương thức khởi tạo trả về "

TÔI

** Trước khi instancetype được giới thiệu trong Objective-C, bộ khởi tạo trả về id (eye-dee). Loại này được định nghĩa là "một con trỏ tới bất kỳ đối tượng nào". (id rất giống void * in C.) Kể từ khi viết bài này, các mẫu lớp XCode vẫn sử dụng id làm kiểu trả về của bộ khởi tạo được thêm vào trong mã soạn sẵn. Không giống như instancetype, id có thể được sử dụng nhiều hơn là một kiểu trả về. Bạn có thể khai báo các biến hoặc tham số phương thức của loại id khi bạn không chắc chắn loại đối tượng nào sẽ kết thúc trỏ đến. Bạn có thể sử dụng id khi sử dụng phép liệt kê nhanh để lặp qua một mảng gồm nhiều loại hoặc nhiều loại đối tượng. Lưu ý rằng vì id không được xác định là "con trỏ tới bất kỳ đối tượng nào", bạn không bao gồm dấu * khi khai báo một biến hoặc tham số đối tượng của loại này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.