Mục tiêu-C: sự khác biệt giữa id và void *


Câu trả lời:


240

void * có nghĩa là "một tham chiếu đến một số bộ nhớ o 'chunk ngẫu nhiên với nội dung chưa được đánh dấu / không xác định"

id có nghĩa là "một tham chiếu đến một số đối tượng Objective-C ngẫu nhiên của lớp chưa biết"

Có sự khác biệt về ngữ nghĩa:

  • Trong chế độ Chỉ hỗ trợ hoặc Chỉ được hỗ trợ của GC, trình biên dịch sẽ phát ra các rào cản ghi cho các tham chiếu loại id, nhưng không phải cho loại void *. Khi khai báo cấu trúc, đây có thể là một sự khác biệt quan trọng. Khai báo iVars như thế void *_superPrivateDoNotTouch;sẽ gây ra sự gặt hái sớm của các đối tượng nếu _superPrivateDoNotTouchthực sự là một đối tượng. Đừng làm vậy.

  • cố gắng gọi một phương thức trên tham chiếu void *kiểu sẽ loại bỏ cảnh báo trình biên dịch.

  • cố gắng gọi một phương thức trên một idkiểu sẽ chỉ cảnh báo nếu phương thức được gọi chưa được khai báo trong bất kỳ @interfacekhai báo nào mà trình biên dịch nhìn thấy.

Vì vậy, người ta không bao giờ nên coi một đối tượng là a void *. Tương tự, người ta nên tránh sử dụng một idbiến được gõ để chỉ một đối tượng. Sử dụng các tài liệu tham khảo lớp cụ thể nhất mà bạn có thể. Thậm chí NSObject *còn tốt hơn idbởi vì ít nhất, trình biên dịch có thể cung cấp xác thực tốt hơn các yêu cầu phương thức đối với tham chiếu đó.

Việc sử dụng phổ biến và hợp lệ void *là dưới dạng tham chiếu dữ liệu mờ được truyền qua một số API khác.

Hãy xem xét sortedArrayUsingFunction: context:phương pháp NSArray:

- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context;

Hàm sắp xếp sẽ được khai báo là:

NSInteger mySortFunc(id left, id right, void *context) { ...; }

Trong trường hợp này, NSArray chỉ chuyển bất cứ thứ gì bạn truyền vào làm contextđối số cho phương thức thông qua như làcontext đối số. Nó là một khối dữ liệu có kích thước con trỏ mờ, theo như NSArray có liên quan, và bạn có thể tự do sử dụng nó cho bất kỳ mục đích nào bạn muốn.

Không có tính năng loại đóng trong ngôn ngữ, đây là cách duy nhất để mang theo một khối dữ liệu có chức năng. Thí dụ; nếu bạn muốn mySortFunc () sắp xếp theo điều kiện là phân biệt chữ hoa chữ thường hoặc không phân biệt chữ hoa chữ thường, trong khi vẫn an toàn cho luồng, bạn sẽ vượt qua chỉ báo phân biệt chữ hoa chữ thường trong ngữ cảnh, có khả năng truyền vào và thoát ra.

Mong manh và dễ bị lỗi, nhưng cách duy nhất.

Các khối giải quyết điều này - Các khối đóng cửa cho C. Chúng có sẵn trong Clang - http://llvm.org/ và có sức lan tỏa trong Snow Leopard ( http://developer.apple.com/l Library / ios / documentation / Formformance /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf ).


Ngoài ra, tôi khá chắc chắn rằng một id giả định được phản hồi -retain-releasetrong khi đó void*hoàn toàn mờ đục đối với callee. Bạn không thể chuyển một con trỏ tùy ý đến -performSelector:withObject:afterDelay:(nó giữ lại đối tượng) và bạn không thể cho rằng nó +[UIView beginAnimations:context:]sẽ giữ lại bối cảnh (đại biểu hoạt hình nên duy trì quyền sở hữu bối cảnh; UIKit giữ lại ủy nhiệm hoạt hình).
tc.

3
Không có giả định có thể được thực hiện về những gì một id phản ứng với. An idcó thể dễ dàng tham khảo một thể hiện của một lớp không có từ đó NSObject. Thực tế, nói, tuyên bố của bạn phù hợp nhất với hành vi trong thế giới thực; bạn không thể trộn các <NSObject>lớp không triển khai với API nền tảng và đi rất xa, điều đó là chắc chắn!
bbum

2
Bây giờ idClasscác loại đang được coi là con trỏ đối tượng có thể giữ lại trong ARC. Vì vậy, giả định là đúng ít nhất là theo ARC.
eonil

"Gặt hái sớm" là gì?
Brad Thomas

1
@BradThomas Khi trình thu gom rác thu thập bộ nhớ trước khi chương trình được thực hiện với nó.
bbum

21

id là một con trỏ tới một đối tượng C, trong đó void * là một con trỏ tới bất cứ thứ gì.

id cũng tắt các cảnh báo liên quan đến việc gọi mthods không xác định, ví dụ:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

sẽ không đưa ra cảnh báo thông thường về các phương pháp chưa biết. Tất nhiên, nó sẽ đưa ra một ngoại lệ trong thời gian chạy trừ khi obj không hoặc thực sự thực hiện phương pháp đó.

Thường thì bạn nên sử dụng NSObject*hoặc id<NSObject>ưu tiên id, điều này ít nhất xác nhận rằng đối tượng được trả về là đối tượng Cacao, vì vậy bạn có thể sử dụng các phương thức như giữ lại / phát hành / tự động trên nó một cách an toàn.


2
Đối với các yêu cầu phương thức, một mục tiêu loại (id) sẽ tạo cảnh báo nếu phương thức được nhắm mục tiêu chưa được khai báo ở bất cứ đâu. Do đó, trong ví dụ của bạn, doS Something WeirdYouveNeverHeardOf sẽ phải được tuyên bố ở đâu đó để không có cảnh báo.
bbum

Bạn là wuite đúng, một ví dụ tốt hơn sẽ là một cái gì đó như StoragePolicy.
Peter N Lewis

@PeterNLewis Tôi không đồng ý Often you should use NSObject*thay vì id. Bằng cách chỉ định NSObject*bạn thực sự nói rõ ràng rằng đối tượng là NSObject. Bất kỳ cuộc gọi phương thức nào đến đối tượng sẽ dẫn đến một cảnh báo, nhưng không có ngoại lệ thời gian chạy miễn là đối tượng đó thực sự đáp ứng cuộc gọi phương thức. Cảnh báo rõ ràng là khó chịu vì vậy idlà tốt hơn. Nói chung id<MKAnnotation>, bạn có thể nói cụ thể hơn bằng cách nói , trong trường hợp này có nghĩa là bất kỳ đối tượng nào, nó phải tuân theo giao thức MKAnnotation.
pnizzle

1
Nếu bạn sẽ sử dụng id <MKAnnotation>, thì bạn cũng có thể sử dụng NSObject <MKAnnotation> *. Sau đó, cho phép bạn sử dụng bất kỳ phương thức nào trong MKAnnotation và bất kỳ phương thức nào trong NSObject (nghĩa là tất cả các đối tượng trong hệ thống phân cấp lớp gốc NSObject bình thường) và nhận được cảnh báo cho bất kỳ điều gì khác, tốt hơn nhiều so với không cảnh báo và một sự cố thời gian chạy.
Peter N Lewis

8

Nếu một phương thức có kiểu trả về, idbạn có thể trả về bất kỳ đối tượng Objective-C nào.

void có nghĩa là, phương thức sẽ không trả lại bất cứ điều gì.

void *chỉ là một con trỏ. Bạn sẽ không thể chỉnh sửa nội dung trên địa chỉ mà con trỏ trỏ tới.


2
Vì nó áp dụng cho giá trị trả về của một phương thức, chủ yếu là đúng. Vì nó áp dụng để khai báo các biến hoặc đối số, không hoàn toàn. Và bạn luôn có thể chuyển (void *) thành một loại cụ thể hơn nếu bạn muốn đọc / ghi nội dung - không phải là một ý tưởng tốt để làm như vậy.
bbum

8

idlà một con trỏ tới một đối tượng Objective-C. void *là một con trỏ đến bất cứ điều gì . Bạn có thể sử dụng void *thay vì id, nhưng không nên vì bạn không bao giờ nhận được cảnh báo về trình biên dịch cho bất cứ điều gì.

Bạn có thể muốn xem stackoverflow.com/questions/466777/whats-the-difference-b between-declaring-a-variable-id- and- nsobjectunixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html .


1
Không hẳn. (void *) các biến được gõ không thể là mục tiêu của các yêu cầu phương thức. Nó dẫn đến "cảnh báo: loại người nhận không hợp lệ 'void *'" từ trình biên dịch.
bbum

@bbum: void *các biến được gõ chắc chắn có thể là mục tiêu của các yêu cầu phương thức - đó là một cảnh báo, không phải là lỗi. Không chỉ vậy, bạn có thể làm điều này: int i = (int)@"Hello, string!";và theo dõi với : printf("Sending to an int: '%s'\n", [i UTF8String]);. Đó là một cảnh báo, không phải là một lỗi (và không chính xác được khuyến nghị, cũng không phải là di động). Nhưng lý do tại sao bạn có thể làm những việc này là tất cả cơ bản C.
johne

1
Lấy làm tiếc. Bạn nói đúng; Đó là một cảnh báo, không phải là một lỗi. Tôi chỉ coi các cảnh báo là lỗi luôn luôn và ở mọi nơi.
bbum

4
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

Đoạn mã trên là từ objc.h, vì vậy có vẻ như id là một thể hiện của objc_object struct và con trỏ isa có thể liên kết với bất kỳ đối tượng Objective C Class nào, trong khi void * chỉ là một con trỏ chưa được gõ.


2

Tôi hiểu rằng id đại diện cho một con trỏ đến một đối tượng trong khi void * có thể trỏ đến bất cứ thứ gì thực sự, miễn là sau đó bạn chuyển nó sang loại bạn muốn sử dụng nó như


Nếu bạn đang chuyển từ (void *) sang một số loại đối tượng, bao gồm id, bạn rất có thể làm sai. Có nhiều lý do để làm như vậy, nhưng chúng rất ít, xa giữa và hầu như luôn luôn chỉ ra một lỗ hổng thiết kế.
bbum

1
trích dẫn "có những lý do để làm như vậy, nhưng chúng là rất ít, xa giữa" đúng như vậy. Nó phụ thuộc vào tình hình. Tuy nhiên tôi sẽ không đưa ra một tuyên bố chăn như "bạn rất có thể làm sai" với một số bối cảnh.
hhafez

Tôi sẽ làm một tuyên bố chăn; đã phải săn lùng và sửa chữa quá nhiều lỗi đáng nguyền rủa vì đúc sai loại với khoảng trống * ở giữa. Một ngoại lệ là các API dựa trên cuộc gọi lại có một đối số ngữ cảnh void * có hợp đồng nói rằng bối cảnh sẽ không bị ảnh hưởng giữa việc thiết lập cuộc gọi lại và nhận cuộc gọi lại.
bbum

0

Ngoài những gì đã nói, có một sự khác biệt giữa các đối tượng và con trỏ liên quan đến các bộ sưu tập. Ví dụ: nếu bạn muốn đặt một cái gì đó vào NSArray, bạn cần một đối tượng (loại "id") và bạn không thể sử dụng một con trỏ dữ liệu thô ở đó (loại "void *"). Bạn có thể sử dụng [NSValue valueWithPointer:rawData]để chuyển đổi void *rawDdatasang loại "id" để sử dụng nó trong bộ sưu tập. Nói chung, "id" linh hoạt hơn và có nhiều ngữ nghĩa liên quan đến các đối tượng gắn liền với nó. Có nhiều ví dụ giải thích loại id của Objective C ở đâ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.