ส่งข้อความถึงศูนย์ใน Objective-C


107

ในฐานะนักพัฒนา Java ที่กำลังอ่านเอกสาร Objective-C 2.0 ของ Apple: ฉันสงสัยว่า " การส่งข้อความถึงศูนย์ " หมายความว่าอย่างไร - นับประสาอะไรกับประโยชน์ที่แท้จริง การตัดตอนมาจากเอกสาร:

มีหลายรูปแบบในโกโก้ที่ใช้ประโยชน์จากข้อเท็จจริงนี้ ค่าที่ส่งคืนจากข้อความถึงศูนย์อาจใช้ได้เช่นกัน:

  • ถ้าเมธอดส่งคืนวัตถุชนิดตัวชี้ใด ๆ สเกลาร์จำนวนเต็มใด ๆ ที่มีขนาดน้อยกว่าหรือเท่ากับ sizeof (โมฆะ *) ลอยคู่คู่ยาวหรือยาวข้อความที่ส่งไปยังศูนย์จะส่งกลับ 0 .
  • ถ้าเมธอดส่งคืนโครงสร้างตามที่กำหนดโดย MacOS X ABI Function Call Guide ที่จะส่งคืนในรีจิสเตอร์ข้อความที่ส่งไปยังศูนย์จะส่งกลับ 0.0 สำหรับทุกฟิลด์ในโครงสร้างข้อมูล ประเภทข้อมูลโครงสร้างอื่น ๆ จะไม่ถูกเติมด้วยศูนย์
  • ถ้าเมธอดส่งคืนสิ่งอื่นนอกเหนือจากค่าที่กล่าวมาให้พิมพ์ค่าส่งคืนของข้อความที่ส่งไปยังศูนย์จะไม่ได้กำหนด

Java ทำให้สมองของฉันไม่สามารถพูดคำอธิบายข้างต้นได้หรือไม่? หรือมีบางอย่างที่ฉันขาดหายไปที่จะทำให้มันใสเหมือนแก้ว?

ฉันจะได้รับความคิดของข้อความ / รับใน Objective-C nilที่ผมกำลังสับสนเพียงเกี่ยวกับการรับที่เกิดขึ้นจะเป็น


2
ฉันมีพื้นหลัง Java ด้วยและฉันรู้สึกกลัวกับคุณสมบัติที่ดีนี้ในตอนแรก แต่ตอนนี้ฉันพบว่ามันน่ารักจริงๆ!;
Valentin Radu

1
ขอบคุณนั่นเป็นคำถามที่ดี คุณเคยมองเห็นถึงประโยชน์ของสิ่งนั้นหรือไม่? มันทำให้ฉันรู้สึกว่า "ไม่ใช่จุดบกพร่อง แต่เป็นคุณลักษณะ" ฉันได้รับข้อบกพร่องอยู่เรื่อย ๆ ซึ่ง Java จะตบฉันด้วยข้อยกเว้นดังนั้นฉันจึงรู้ว่าปัญหาอยู่ที่ไหน ฉันไม่พอใจที่จะแลกเปลี่ยนข้อยกเว้นตัวชี้โมฆะเพื่อบันทึกรหัสเล็กน้อยหนึ่งหรือสองบรรทัดที่นี่และที่นั่น
Maciej Trybiło

คำตอบ:


92

ฉันคิดว่ามันสามารถอธิบายได้โดยใช้ตัวอย่างที่ออกแบบไว้มาก สมมติว่าคุณมีวิธีการใน Java ที่พิมพ์องค์ประกอบทั้งหมดใน ArrayList:

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

ทีนี้ถ้าคุณเรียกวิธีนั้นว่า someObject.foo (NULL); คุณอาจจะได้รับ NullPointerException เมื่อพยายามเข้าถึงรายการในกรณีนี้ในการเรียก list.size (); ตอนนี้คุณอาจไม่เคยเรียก someObject.foo (NULL) ด้วยค่า NULL แบบนั้น อย่างไรก็ตามคุณอาจได้รับ ArrayList ของคุณจากวิธีการที่ส่งคืนค่า NULL หากพบข้อผิดพลาดในการสร้าง ArrayList เช่น someObject.foo (otherObject.getArrayList ());

แน่นอนคุณจะมีปัญหาเช่นกันหากคุณทำสิ่งนี้:

ArrayList list = NULL;
list.size();

ตอนนี้ใน Objective-C เรามีวิธีการเทียบเท่า:

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

ตอนนี้ถ้าเรามีรหัสต่อไปนี้:

[someObject foo:nil];

เรามีสถานการณ์เดียวกันกับที่ Java จะสร้าง NullPointerException วัตถุศูนย์จะถูกเข้าถึงก่อนที่ [anArray count] อย่างไรก็ตามแทนที่จะโยน NullPointerException Objective-C จะคืนค่า 0 ตามกฎด้านบนดังนั้นลูปจะไม่ทำงาน อย่างไรก็ตามหากเราตั้งค่าลูปให้รันตามจำนวนครั้งที่กำหนดเราจะส่งข้อความไปยัง anArray ก่อนที่ [anArray objectAtIndex: i]; สิ่งนี้จะส่งกลับ 0 เช่นกัน แต่เนื่องจาก objectAtIndex: ส่งกลับตัวชี้และตัวชี้ไปที่ 0 เป็นศูนย์ / NULL NSLog จะถูกส่งผ่านศูนย์ทุกครั้งที่ผ่านลูป (แม้ว่า NSLog จะเป็นฟังก์ชันและไม่ใช่เมธอด แต่จะพิมพ์ออกมา (null) หากผ่าน nil NSString

ในบางกรณีการมี NullPointerException จะดีกว่าเนื่องจากคุณสามารถบอกได้ทันทีว่ามีบางอย่างผิดปกติกับโปรแกรม แต่ถ้าคุณไม่พบข้อยกเว้นโปรแกรมจะหยุดทำงาน (ใน C การพยายามหักล้างค่า NULL ด้วยวิธีนี้จะทำให้โปรแกรมหยุดทำงาน) ใน Objective-C จะทำให้เกิดพฤติกรรมรันไทม์ที่อาจไม่ถูกต้องแทน อย่างไรก็ตามหากคุณมีเมธอดที่ไม่ทำลายหากส่งกลับ 0 / nil / NULL / a zeroed struct สิ่งนี้จะช่วยให้คุณไม่ต้องตรวจสอบเพื่อให้แน่ใจว่าอ็อบเจ็กต์หรือพารามิเตอร์เป็นศูนย์


33
อาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าพฤติกรรมนี้เป็นประเด็นที่มีการถกเถียงกันมากในชุมชน Objective-C ในช่วงสองสามทศวรรษที่ผ่านมา การแลกเปลี่ยนระหว่าง "ความปลอดภัย" และ "ความสะดวกสบาย" นั้นได้รับการประเมินที่แตกต่างกันโดยคนที่แตกต่างกัน
Mark Bessey

3
ในทางปฏิบัติมีความสมมาตรระหว่างการส่งข้อความไปยังศูนย์และวิธีการทำงานของ Objective-C โดยเฉพาะอย่างยิ่งในคุณสมบัติตัวชี้จุดอ่อนใหม่ใน ARC ตัวชี้ที่อ่อนแอจะเป็นศูนย์โดยอัตโนมัติ ดังนั้นออกแบบ API ของคุณเพื่อให้สามารถตอบสนองต่อ 0 / nil / NIL / NULL เป็นต้น
Cthutu

1
ฉันคิดว่าถ้าคุณทำmyObject->iVarมันจะพังไม่ว่า C จะมีหรือไม่มีวัตถุก็ตาม (ขออภัยที่ gravedig.)
11684

3
@ 11684 ถูกต้อง แต่->ไม่ใช่การดำเนินการ Objective-C อีกต่อไป แต่เป็น C-ism ทั่วไป
bbum

1
ราก OSX ที่ผ่านมาใช้ประโยชน์ / ซ่อน API ลับๆสามารถเข้าถึงได้สำหรับผู้ใช้ทั้งหมด (ไม่ได้เป็นเพียงผู้ดูแลระบบ) เพราะ obj-C ของการส่งข้อความ Nil
dcow


41

โพสต์อื่น ๆ ทั้งหมดถูกต้อง แต่อาจเป็นแนวคิดที่เป็นสิ่งสำคัญที่นี่

ในการเรียกเมธอด Objective-C การอ้างอิงอ็อบเจ็กต์ใด ๆ ที่สามารถยอมรับตัวเลือกเป็นเป้าหมายที่ถูกต้องสำหรับตัวเลือกนั้น

สิ่งนี้ช่วยประหยัดได้มาก "เป็นวัตถุเป้าหมายประเภท X หรือไม่" รหัส - ตราบใดที่วัตถุที่ได้รับใช้ตัวเลือกมันจะไม่สร้างความแตกต่างอย่างแน่นอนว่ามันคือคลาสอะไร! nilเป็น NSObject ที่ยอมรับตัวเลือกใด ๆ - มันไม่ได้ทำอะไรเลย ซึ่งจะช่วยลดรหัส "check for nil, don't send the message if true" ได้มากเช่นกัน (แนวคิด "ถ้ายอมรับก็จะใช้มัน" ยังเป็นสิ่งที่ช่วยให้คุณสามารถสร้างโปรโตคอลซึ่งเป็นแบบ sorta เหมือนกับอินเตอร์เฟส Java: เป็นการประกาศว่าหากคลาสใช้วิธีการที่ระบุไว้ก็จะเป็นไปตามโปรโตคอล)

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

การชี้แจงสำหรับผู้ลงคะแนน: คุณอาจคิดว่านี่ไม่ใช่วิธีที่ดี แต่เป็นวิธีการใช้ภาษาและเป็นสำนวนการเขียนโปรแกรมที่แนะนำใน Objective-C (ดูการบรรยายการเขียนโปรแกรม iPhone ของ Stanford)


17

ความหมายคือรันไทม์ไม่สร้างข้อผิดพลาดเมื่อเรียก objc_msgSend บนตัวชี้ศูนย์ แต่จะส่งคืนค่าบางค่า (มักมีประโยชน์) ข้อความที่อาจมีผลข้างเคียงไม่ทำอะไรเลย

มีประโยชน์เนื่องจากค่าเริ่มต้นส่วนใหญ่เหมาะสมกว่าข้อผิดพลาด ตัวอย่างเช่น:

[someNullNSArrayReference count] => 0

กล่าวคือศูนย์ดูเหมือนจะเป็นอาร์เรย์ว่างเปล่า การซ่อนการอ้างอิง nil NSView ไม่ได้ทำอะไรเลย สะดวกใช่มั้ย?


12

ในใบเสนอราคาจากเอกสารมีสองแนวคิดแยกกัน - บางทีอาจจะดีกว่าถ้าเอกสารประกอบทำให้ชัดเจนมากขึ้น:

มีหลายรูปแบบในโกโก้ที่ใช้ประโยชน์จากข้อเท็จจริงนี้

ค่าที่ส่งคืนจากข้อความถึงศูนย์อาจใช้ได้เช่นกัน:

ในอดีตอาจมีความเกี่ยวข้องมากกว่าที่นี่: โดยปกติแล้วความสามารถในการส่งข้อความเพื่อnilทำให้โค้ดตรงไปตรงมามากขึ้น - คุณไม่จำเป็นต้องตรวจสอบค่า null ทุกที่ ตัวอย่าง Canonical น่าจะเป็นวิธีการเข้าถึง:

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

หากการส่งข้อความไปnilไม่ถูกต้องวิธีนี้จะซับซ้อนมากขึ้น - คุณต้องมีการตรวจสอบเพิ่มเติมอีกสองครั้งเพื่อให้แน่ใจvalueและnewValueไม่ใช่nilก่อนที่จะส่งข้อความ

ประเด็นหลัง (ค่าที่ส่งคืนจากข้อความถึงnilก็ใช้ได้เช่นกัน) แม้ว่าจะเพิ่มเอฟเฟกต์ตัวคูณให้กับอดีต ตัวอย่างเช่น:

if ([myArray count] > 0) {
    // do something...
}

รหัสนี้ไม่ต้องการการตรวจสอบnilค่าอีกครั้งและไหลตามธรรมชาติ ...

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


12

จากเว็บไซต์ของGreg Parker :

หากใช้ LLVM Compiler 3.0 (Xcode 4.2) หรือใหม่กว่า

ข้อความที่เป็นศูนย์ด้วยประเภทการส่งคืน | กลับ
จำนวนเต็มสูงสุด 64 บิต | 0
จุดลอยตัวขึ้นไปคู่ยาว | 0.0
ตัวชี้ | ศูนย์
โครงสร้าง | {0}
_Complex ประเภทใดก็ได้ | {0, 0}

9

หมายความว่ามักจะไม่ต้องตรวจหาวัตถุที่เป็นศูนย์ทุกที่เพื่อความปลอดภัยโดยเฉพาะ:

[someVariable release];

หรือตามที่ระบุไว้วิธีการนับและความยาวต่างๆทั้งหมดจะคืนค่า 0 เมื่อคุณมีค่าศูนย์ดังนั้นคุณไม่จำเป็นต้องเพิ่มการตรวจสอบเพิ่มเติมสำหรับศูนย์ทั้งหมด:

if ( [myString length] > 0 )

หรือสิ่งนี้:

return [myArray count]; // say for number of rows in a table

โปรดทราบว่าอีกด้านหนึ่งของเหรียญมีโอกาสเกิดจุดบกพร่องเช่น "if ([myString length] == 1)"
hatfinch

จุดบกพร่องนั้นเป็นอย่างไร [myString length] จะคืนค่าศูนย์ (ศูนย์) หาก myString เป็นศูนย์ ... สิ่งหนึ่งที่ฉันคิดว่าอาจเป็นปัญหาคือ [myView frame] ซึ่งฉันคิดว่าสามารถให้บางสิ่งที่แปลกประหลาดแก่คุณได้หาก myView เป็นศูนย์
Kendall Helmstetter Gelner

หากคุณออกแบบคลาสและวิธีการตามแนวคิดที่ว่าค่าเริ่มต้น (0, ศูนย์, NO) หมายถึง "ไม่มีประโยชน์" นี่เป็นเครื่องมือที่มีประสิทธิภาพ ฉันไม่ต้องตรวจสอบสตริงของฉันก่อนที่จะตรวจสอบความยาว สำหรับฉันแล้วสตริงว่างเปล่านั้นไร้ประโยชน์และเป็นสตริงศูนย์เมื่อฉันกำลังประมวลผลข้อความ ฉันเป็นนักพัฒนา Java ด้วยและฉันรู้ว่าคนเจ้าระเบียบ Java จะหลีกเลี่ยงสิ่งนี้ แต่มันช่วยประหยัดการเข้ารหัสได้มาก
Jason Fuerstenberg

6

อย่าคิดว่า "ผู้รับเป็นศูนย์"; ฉันยอมรับว่ามันค่อนข้างแปลก หากคุณส่งข้อความถึงศูนย์แสดงว่าไม่มีผู้รับ คุณแค่ส่งข้อความไปเฉยๆ

วิธีจัดการกับความแตกต่างทางปรัชญาระหว่าง Java และ Objective-C: ใน Java นั่นเป็นข้อผิดพลาด ใน Objective-C มันเป็น no-op


มีข้อยกเว้นสำหรับพฤติกรรมนั้นใน java ถ้าคุณเรียกใช้ฟังก์ชันแบบคงที่บน null มันจะเทียบเท่ากับการเรียกใช้ฟังก์ชันบนคลาสเวลาคอมไพล์ของตัวแปร (ไม่ว่าจะเป็นค่าว่าง)
Roman A.Taycher

6

ข้อความ ObjC ซึ่งถูกส่งไปยังศูนย์และมีค่าที่ส่งคืนมีขนาดใหญ่กว่า sizeof (void *) จะสร้างค่าที่ไม่ได้กำหนดบนโปรเซสเซอร์ PowerPC นอกจากนั้นข้อความเหล่านี้ยังทำให้ค่าที่ไม่ได้กำหนดถูกส่งกลับในฟิลด์ของโครงสร้างที่มีขนาดใหญ่กว่า 8 ไบต์บนโปรเซสเซอร์ Intel ด้วย Vincent Gable ได้อธิบายสิ่งนี้ไว้อย่างดีในบล็อกโพสต์ของเขา


6

ฉันไม่คิดว่าคำตอบอื่น ๆ ได้กล่าวถึงสิ่งนี้อย่างชัดเจน: หากคุณคุ้นเคยกับ Java คุณควรจำไว้ว่าแม้ว่า Objective-C บน Mac OS X จะมีการรองรับการจัดการข้อยกเว้น แต่ก็เป็นคุณลักษณะภาษาเสริมที่สามารถทำได้ เปิด / ปิดด้วยแฟล็กคอมไพเลอร์ ฉันเดาว่าการออกแบบ "การส่งข้อความถึงnilปลอดภัย" นี้มีมาก่อนการรวมการสนับสนุนการจัดการข้อยกเว้นในภาษาและทำโดยมีเป้าหมายที่คล้ายกัน: วิธีการต่างๆสามารถย้อนกลับnilเพื่อระบุข้อผิดพลาดและเนื่องจากการส่งข้อความไปnilมักจะส่งคืนnilในทางกลับกันสิ่งนี้ทำให้ตัวบ่งชี้ข้อผิดพลาดเผยแพร่ผ่านรหัสของคุณดังนั้นคุณจึงไม่ต้องตรวจสอบในทุกข้อความ คุณต้องตรวจสอบในจุดที่สำคัญเท่านั้น โดยส่วนตัวแล้วฉันคิดว่าการเผยแพร่ข้อยกเว้นและการจัดการเป็นวิธีที่ดีกว่าในการบรรลุเป้าหมายนี้ แต่ไม่ใช่ทุกคนที่เห็นด้วยกับสิ่งนั้น (ในทางกลับกันยกตัวอย่างเช่นฉันไม่ชอบข้อกำหนดของ Java ที่คุณต้องประกาศว่ามีข้อยกเว้นใดบ้างที่เมธอดอาจส่งผลให้ซึ่งมักบังคับให้คุณเผยแพร่การประกาศข้อยกเว้นทางวากยสัมพันธ์ตลอดทั้งรหัสของคุณ แต่นั่นคือการสนทนาอื่น)

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


ฉันไม่เคยคิดแบบนั้นมาก่อน ดูเหมือนจะเป็นคุณสมบัติที่สะดวกมาก
mk12

2
เดาได้ดี แต่ในอดีตไม่ถูกต้องเกี่ยวกับสาเหตุที่ตัดสินใจ การจัดการข้อยกเว้นมีอยู่ในภาษาตั้งแต่เริ่มต้นแม้ว่าตัวจัดการข้อยกเว้นดั้งเดิมจะค่อนข้างดั้งเดิมเมื่อเปรียบเทียบกับสำนวนสมัยใหม่ Nil-ate-message เป็นตัวเลือกการออกแบบที่ใส่ใจซึ่งได้มาจากพฤติกรรมทางเลือกของวัตถุ Nil ใน Smalltalk เมื่อ NeXTSTEP API ดั้งเดิมได้รับการออกแบบการเชื่อมต่อวิธีการเป็นเรื่องปกติธรรมดาและการnilกลับมาใช้บ่อยครั้งในการลัดวงจรโซ่ให้เป็น NO-op
bbum

2

C แทนค่า 0 สำหรับค่าดั้งเดิมและ NULL สำหรับพอยน์เตอร์ (ซึ่งเทียบเท่ากับ 0 ในบริบทพอยน์เตอร์)

Objective-C สร้างขึ้นจากการแทนค่าของ C โดยการเพิ่มศูนย์ ศูนย์เป็นตัวชี้วัตถุที่ไม่มีอะไร แม้ว่าจะมีความหมายแตกต่างจาก NULL แต่ในทางเทคนิคก็มีความเท่าเทียมกัน

NSObjects ที่จัดสรรใหม่เริ่มต้นชีวิตด้วยเนื้อหาที่ตั้งค่าเป็น 0 ซึ่งหมายความว่าพอยน์เตอร์ทั้งหมดที่อ็อบเจ็กต์มีต่ออ็อบเจ็กต์อื่น ๆ จะเริ่มต้นเป็นศูนย์ดังนั้นจึงไม่จำเป็นที่จะต้องตั้งค่าด้วยตนเอง (การเชื่อมโยง) = ศูนย์ในวิธีการเริ่มต้น

พฤติกรรมที่โดดเด่นที่สุดของศูนย์คือสามารถมีข้อความที่ส่งถึงมันได้

ในภาษาอื่น ๆ เช่น C ++ (หรือ Java) สิ่งนี้จะทำให้โปรแกรมของคุณขัดข้อง แต่ใน Objective-C การเรียกใช้เมธอดบนศูนย์จะส่งกลับค่าเป็นศูนย์ สิ่งนี้ช่วยลดความซับซ้อนของนิพจน์ได้อย่างมากเนื่องจากไม่จำเป็นต้องตรวจสอบศูนย์ก่อนที่จะทำอะไร:

// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

การตระหนักถึงวิธีการทำงานของศูนย์ใน Objective-C ช่วยให้ความสะดวกสบายนี้เป็นคุณสมบัติและไม่ใช่ข้อบกพร่องที่ซ่อนอยู่ในแอปพลิเคชันของคุณ ตรวจสอบให้แน่ใจว่าได้ป้องกันกรณีที่ค่าศูนย์ไม่เป็นที่ต้องการไม่ว่าจะโดยการตรวจสอบและส่งคืนก่อนกำหนดเพื่อล้มเหลวโดยไม่โต้ตอบหรือเพิ่ม NSParameterAssert เพื่อโยนข้อยกเว้น

ที่มา: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (การส่งข้อความถึงศูนย์)

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