วัตถุประสงค์ -C: ความแตกต่างระหว่าง id และ void *


คำตอบ:


240

void * หมายถึง "การอ้างอิงถึงหน่วยความจำบางส่วนแบบสุ่มที่มีเนื้อหาที่ไม่ได้พิมพ์ / ไม่รู้จัก"

id หมายถึง "การอ้างอิงถึงบาง Objective-C วัตถุของคลาสที่ไม่รู้จัก"

มีความแตกต่างทางความหมายเพิ่มเติม:

  • ภายใต้ GC เท่านั้นหรือ GC โหมดสนับสนุนคอมไพเลอร์จะปล่อยอุปสรรคเขียนอ้างอิงจากประเภทแต่ไม่สำหรับประเภทid void *เมื่อประกาศโครงสร้างสิ่งนี้อาจเป็นความแตกต่างที่สำคัญ การประกาศ iVars เช่นvoid *_superPrivateDoNotTouch;นั้นจะทำให้เกิดการเก็บเกี่ยววัตถุก่อนกำหนดหาก_superPrivateDoNotTouchจริง ๆ แล้วเป็นวัตถุ อย่าทำอย่างนั้น

  • ความพยายามที่จะเรียกใช้เมธอดบนการอ้างอิงvoid *ชนิดจะทำให้เกิดคำเตือนคอมไพเลอร์

  • ความพยายามที่จะเรียกใช้เมธอดบนidชนิดจะเตือนเฉพาะเมื่อเมธอดที่ถูกเรียกนั้นไม่ได้ถูกประกาศในการประกาศใด ๆ@interfaceที่คอมไพเลอร์เห็น

void *ดังนั้นหนึ่งไม่ควรอ้างถึงวัตถุที่เป็นหนึ่ง ในทำนองเดียวกันเราควรหลีกเลี่ยงการใช้idตัวแปรที่พิมพ์เพื่ออ้างถึงวัตถุ ใช้การอ้างอิงคลาสที่เฉพาะเจาะจงมากที่สุดที่คุณสามารถทำได้ แม้NSObject *จะดีกว่าidเพราะอย่างน้อยที่สุดคอมไพเลอร์สามารถให้การตรวจสอบความถูกต้องของการเรียกใช้เมธอดเทียบกับการอ้างอิงนั้นได้ดีขึ้น

การใช้งานทั่วไปอย่างหนึ่งที่ถูกต้องvoid *คือการอ้างอิงข้อมูลทึบแสงที่ส่งผ่าน API อื่น

พิจารณาsortedArrayUsingFunction: context:วิธีการNSArray:

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

ฟังก์ชันการเรียงลำดับจะถูกประกาศเป็น:

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

ในกรณีนี้ NSArray จะส่งผ่านสิ่งที่คุณส่งผ่านเป็นcontextอาร์กิวเมนต์ไปยังวิธีที่ผ่านเป็นcontextอาร์กิวเมนต์ มันเป็นก้อนขนาดทึบข้อมูลขนาดตัวชี้เท่าที่ NSArray เป็นห่วงและคุณมีอิสระที่จะใช้เพื่อวัตถุประสงค์ใดก็ตามที่คุณต้องการ

หากไม่มีคุณสมบัติการปิดในภาษานี่เป็นวิธีเดียวที่จะพกพาข้อมูลขนาดใหญ่ไปพร้อมกับฟังก์ชั่น ตัวอย่าง; หากคุณต้องการให้ mySortFunc () เรียงลำดับตามตัวพิมพ์เล็กและใหญ่โดยไม่คำนึงถึงขนาดตัวพิมพ์เล็กและตัวพิมพ์เล็กในขณะที่ยังคงปลอดภัยต่อเธรด

เปราะบางและผิดพลาดได้ง่าย แต่วิธีเดียว

บล็อกแก้ปัญหานี้ - บล็อกปิดสำหรับ C. มีอยู่ใน Clang - http://llvm.org/และแพร่หลายใน Snow Leopard ( http://developer.apple.com/library/ios/documentation/Performance) /Reference/GCD_libdispatch_Ref/GCD_libdispatch_Ref.pdf )


นอกจากนี้ฉันค่อนข้างแน่ใจว่าidจะถือว่ามีการตอบสนอง-retainและ-releaseในขณะที่ a void*ทึบแสงอย่างสมบูรณ์เพื่อ callee คุณไม่สามารถส่งตัวชี้ไปยัง-performSelector:withObject:afterDelay:(มันเก็บรักษาวัตถุ) และคุณไม่สามารถสันนิษฐานได้ว่า+[UIView beginAnimations:context:]จะรักษาบริบทไว้ (ตัวแทนภาพเคลื่อนไหวควรรักษาความเป็นเจ้าของบริบทไว้ UIKit จะรักษาตัวแทนภาพเคลื่อนไหว)
tc

3
ไม่สามารถตั้งสมมติฐานเกี่ยวกับสิ่งที่idตอบสนองได้ สามารถอ้างถึงตัวอย่างของการเรียนที่ไม่ได้มาจากธรรมชาติid NSObjectอย่างไรก็ตามคำพูดของคุณในทางปฏิบัติจะตรงกับพฤติกรรมโลกแห่งความเป็นจริงมากที่สุด คุณไม่สามารถผสม<NSObject>คลาสที่ไม่ได้ใช้งานกับ Foundation API และไปได้ไกลนั่นคือแน่นอน!
bbum

2
ตอนนี้idและClassชนิดจะได้รับการปฏิบัติเป็นตัวชี้วัตถุที่เก็บรักษาไว้ภายใต้ ARC ดังนั้นสมมติฐานจึงเป็นจริงอย่างน้อยภายใต้ ARC
eonil

"การเก็บเกี่ยวก่อนกำหนด" คืออะไร
แบรดโทมัส

1
@BradThomas เมื่อตัวรวบรวมขยะรวบรวมหน่วยความจำก่อนที่โปรแกรมจะทำงานเสร็จ
bbum

21

id เป็นตัวชี้ไปยังวัตถุ C วัตถุประสงค์ที่เป็นโมฆะ * เป็นตัวชี้ไปที่อะไร

id ยังปิดคำเตือนที่เกี่ยวข้องกับการโทร mthods ที่ไม่รู้จักดังนั้นตัวอย่างเช่น:

[(id)obj doSomethingWeirdYouveNeverHeardOf];

จะไม่ให้คำเตือนตามปกติเกี่ยวกับวิธีการที่ไม่รู้จัก แน่นอนว่าจะเพิ่มข้อยกเว้น ณ รันไทม์ยกเว้นว่า obj ไม่มีค่าหรือใช้วิธีการนั้นจริงๆ

บ่อยครั้งที่คุณควรใช้NSObject*หรือid<NSObject>ตั้งค่าidอย่างน้อยที่สุดยืนยันว่าวัตถุที่ส่งคืนเป็นวัตถุโกโก้ดังนั้นคุณจึงสามารถใช้วิธีการต่างๆเช่นเก็บรักษา / ปล่อย / เลิกอัตโนมัติโดยอัตโนมัติ


2
สำหรับการเรียกใช้เมธอดเป้าหมายประเภท (id) จะสร้างคำเตือนหากวิธีการเป้าหมายไม่ได้รับการประกาศที่ใดก็ได้ ดังนั้นในตัวอย่างของคุณทำอะไรบางอย่างแปลก ๆ คุณไม่เคยได้ยินว่าจะต้องมีการประกาศที่ไหนสักแห่งเพราะจะไม่มีการเตือน
bbum

คุณพูดถูกแล้วตัวอย่างที่ดีกว่าน่าจะเป็นที่เก็บข้อมูลนโยบายสาธารณะ
Peter N Lewis

@PeterNLewis ผมไม่เห็นด้วยกับแทนOften you should use NSObject* idโดยการระบุว่าNSObject*คุณกำลังบอกอย่างชัดเจนว่าวัตถุนั้นเป็น NSObject การเรียกใช้เมธอดไปยังวัตถุใด ๆ จะส่งผลให้เกิดการเตือน แต่ไม่มีข้อยกเว้นรันไทม์ตราบเท่าที่วัตถุนั้นตอบสนองการเรียกเมธอด เห็นได้ชัดว่าคำเตือนน่ารำคาญดังนั้นidดีกว่า คุณสามารถระบุให้ชัดเจนยิ่งขึ้นโดยการพูดอย่างหยาบid<MKAnnotation>ซึ่งในกรณีนี้หมายถึงสิ่งใดก็ตามวัตถุนั้นจะต้องสอดคล้องกับโปรโตคอล MKAnnotation
pnizzle

1
หากคุณกำลังจะใช้ id <MKAnnotation> คุณก็สามารถใช้ NSObject <MKAnnotation> * ได้เช่นกัน ซึ่งอนุญาตให้คุณใช้วิธีการใด ๆ ใน MKAnnotation และวิธีการใด ๆ ใน NSObject (เช่นวัตถุทั้งหมดในลำดับชั้นรากคลาส NSObject ปกติ) และรับคำเตือนสำหรับสิ่งอื่นซึ่งดีกว่าไม่มีการเตือนและ รันไทม์ขัดข้อง
Peter N Lewis

8

หากวิธีการมีชนิดส่งคืนของidคุณอาจส่งคืนวัตถุ Objective-C ใด ๆ

void หมายความว่าวิธีจะไม่ส่งคืนสิ่งใด

void *เป็นเพียงตัวชี้ คุณจะไม่สามารถแก้ไขเนื้อหาในที่อยู่ที่ตัวชี้ชี้ไป


2
ตามที่ใช้กับค่าส่งคืนของวิธีการส่วนใหญ่ถูกต้อง มันใช้กับการประกาศตัวแปรหรือข้อโต้แย้งไม่มาก และคุณสามารถส่ง (เป็นโมฆะ *) ไปยังประเภทที่เฉพาะเจาะจงมากขึ้นถ้าคุณต้องการอ่าน / เขียนเนื้อหา - ไม่ใช่ว่ามันเป็นความคิดที่ดีที่จะทำ
bbum

8

idเป็นตัวชี้ไปยังวัตถุ Objective-C void *เป็นตัวชี้เพื่ออะไร คุณสามารถใช้void *แทนidแต่ไม่แนะนำเพราะคุณจะไม่ได้รับคำเตือนของคอมไพเลอร์สำหรับสิ่งใด

คุณอาจต้องการดูstackoverflow.com/questions/466777/whats-the-difference-between-declaring-a-variable-id-and-nsobjectและunixjunkie.blogspot.com/2008/03/id-vs-nsobject-vs -id.html


1
ไม่มาก (void *) ตัวแปรที่พิมพ์ไม่สามารถเป็นเป้าหมายของการเรียกใช้เมธอดได้เลย มันส่งผลให้ "คำเตือน: ประเภทตัวรับที่ไม่ถูกต้อง 'void *'" จากคอมไพเลอร์
bbum

@bbum: void *ตัวแปรที่พิมพ์มากที่สุดแน่นอนสามารถเป็นเป้าหมายของวิธีการ invocations- มันเป็นคำเตือนไม่ใช่ข้อผิดพลาด ไม่เพียง แต่ที่คุณสามารถทำสิ่งนี้และตามด้วย:int i = (int)@"Hello, string!"; printf("Sending to an int: '%s'\n", [i UTF8String]);มันเป็นคำเตือนไม่ใช่ข้อผิดพลาด (และไม่แนะนำอย่างแน่นอนหรือพกพา) แต่เหตุผลที่คุณสามารถทำสิ่งเหล่านี้ได้นั้นเป็นพื้นฐานขั้นพื้นฐานทั้งหมด
johne

1
ขอโทษ คุณถูก; มันเป็นคำเตือนไม่ใช่ข้อผิดพลาด ฉันแค่ถือว่าคำเตือนเป็นข้อผิดพลาดเสมอและทุกที่
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;

รหัสข้างต้นมาจาก objc.h ดังนั้นดูเหมือนว่า id เป็นตัวอย่างของ objc_object struct และตัวชี้ isa สามารถผูกกับวัตถุคลาส C Objective ใด ๆ ในขณะที่โมฆะ * เป็นเพียงตัวชี้ที่ไม่ได้พิมพ์


2

ความเข้าใจของฉันคือ id แสดงถึงตัวชี้ไปยังวัตถุในขณะที่โมฆะ * สามารถชี้ไปที่สิ่งใด ๆ จริง ๆ ตราบใดที่คุณโยนมันไปยังประเภทที่คุณต้องการใช้เป็น


หากคุณกำลังส่งข้อมูลจาก (void *) ไปยังวัตถุบางประเภทรวมถึงรหัสคุณมีแนวโน้มที่จะทำผิด มีเหตุผลในการทำเช่นนั้น แต่มีน้อยอยู่ระหว่างและเกือบจะบ่งบอกถึงข้อบกพร่องในการออกแบบ
bbum

1
อ้างว่า "มีเหตุผลที่ต้องทำเช่นนั้น แต่ก็มีอยู่น้อยมาก" ที่จริง มันขึ้นอยู่กับสถานการณ์ อย่างไรก็ตามฉันจะไม่ทำคำสั่งแบบครอบคลุมเช่น "คุณมีโอกาสมากที่จะทำผิด" โดยไม่มีบริบท
hhafez

ฉันจะทำผ้าห่ม; ต้องตามล่าและแก้ไขข้อผิดพลาดที่ถูกสาปมากเกินไปเนื่องจากการทำการพิมพ์ผิดกับช่องว่าง * ในระหว่างนั้น ข้อยกเว้นเดียวคือ API ที่ใช้การติดต่อกลับที่ใช้อาร์กิวเมนต์ void * ซึ่งสัญญาระบุว่าบริบทนั้นจะยังคงไม่ถูกแตะต้องระหว่างการตั้งค่าการโทรกลับและการรับการติดต่อกลับ
bbum

0

นอกเหนือจากสิ่งที่ได้กล่าวไปแล้วมีความแตกต่างระหว่างวัตถุและตัวชี้ที่เกี่ยวข้องกับคอลเลกชัน ตัวอย่างเช่นหากคุณต้องการใส่บางสิ่งลงใน NSArray คุณต้องมีวัตถุ (ชนิด "id") และคุณไม่สามารถใช้ตัวชี้ข้อมูลดิบที่นั่น (ประเภท "void *") คุณสามารถใช้[NSValue valueWithPointer:rawData]เพื่อแปลงvoid *rawDdataเป็น "id" เพื่อใช้ภายในคอลเลกชัน โดยทั่วไป "id" มีความยืดหยุ่นมากขึ้นและมีความหมายมากขึ้นเกี่ยวกับวัตถุที่แนบมา มีตัวอย่างอื่น ๆ อธิบายเป็นรหัสประเภทของวัตถุประสงค์ C ที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.