สารละลาย
คอมไพเลอร์เตือนเกี่ยวกับเรื่องนี้ด้วยเหตุผล มันยากมากที่คำเตือนนี้ควรถูกเพิกเฉยและง่ายต่อการแก้ไข นี่คือวิธี:
if (!_controller) { return; }
SEL selector = NSSelectorFromString(@"someMethod");
IMP imp = [_controller methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(_controller, selector);
หรือมากกว่านั้น (อ่านยากและไม่มีผู้พิทักษ์):
SEL selector = NSSelectorFromString(@"someMethod");
((void (*)(id, SEL))[_controller methodForSelector:selector])(_controller, selector);
คำอธิบาย
สิ่งที่เกิดขึ้นที่นี่คือคุณกำลังถามตัวควบคุมสำหรับตัวชี้ฟังก์ชัน C สำหรับวิธีการที่สอดคล้องกับตัวควบคุม NSObjectตอบกลับทั้งหมดmethodForSelector:แต่คุณยังสามารถใช้class_getMethodImplementationใน Objective-C runtime (มีประโยชน์หากคุณมีการอ้างอิงโปรโตคอลเท่านั้นid<SomeProto>) เหล่านี้คำแนะนำการทำงานที่เรียกว่าIMPs และมีความเรียบง่ายtypedefคำแนะนำการทำงานเอ็ด ( id (*IMP)(id, SEL, ...)) 1 นี่อาจใกล้เคียงกับลายเซ็นวิธีการที่แท้จริงของวิธีการ แต่จะไม่ตรงกันทุกครั้ง
เมื่อคุณมีIMPแล้วคุณจะต้องโยนมันไปยังตัวชี้ฟังก์ชั่นที่มีรายละเอียดทั้งหมดที่ ARC ต้องการ (รวมถึงอาร์กิวเมนต์ที่ซ่อนอยู่สองนัยselfและ_cmdของการเรียกเมธอด Objective-C ทุกครั้ง) สิ่งนี้ถูกจัดการในบรรทัดที่สาม ( (void *)ทางด้านขวามือจะบอกคอมไพเลอร์ว่าคุณรู้ว่าคุณกำลังทำอะไรและไม่สร้างคำเตือนเนื่องจากประเภทตัวชี้ไม่ตรงกัน)
สุดท้ายคุณเรียกตัวชี้ฟังก์ชั่น2
ตัวอย่างที่ซับซ้อน
เมื่อตัวเลือกรับอาร์กิวเมนต์หรือคืนค่าคุณจะต้องเปลี่ยนสิ่งต่าง ๆ เล็กน้อย:
SEL selector = NSSelectorFromString(@"processRegion:ofView:");
IMP imp = [_controller methodForSelector:selector];
CGRect (*func)(id, SEL, CGRect, UIView *) = (void *)imp;
CGRect result = _controller ?
func(_controller, selector, someRect, someView) : CGRectZero;
เหตุผลสำหรับคำเตือน
เหตุผลสำหรับการเตือนนี้คือด้วย ARC รันไทม์จำเป็นต้องรู้ว่าจะทำอย่างไรกับผลของวิธีการที่คุณโทร ผลที่ได้จะเป็นอะไร: void, int, char, NSString *, idฯลฯ ARC ปกติได้รับข้อมูลนี้จากส่วนหัวของชนิดของวัตถุที่คุณกำลังทำงานกับ 3
มีเพียง 4 สิ่งเท่านั้นที่ ARC จะพิจารณาสำหรับค่าส่งคืน: 4
- ประเภทไม่สนใจไม่ใช่วัตถุ (
void, intฯลฯ )
- เก็บค่าของออบเจ็กต์จากนั้นปล่อยเมื่อไม่ใช้งานอีกต่อไป (ข้อสมมติฐานมาตรฐาน)
- ปล่อยค่าออบเจคใหม่เมื่อไม่ใช้งานอีกต่อไป (วิธีการใน
init/ copyfamily หรือประกอบกับns_returns_retained)
- ทำอะไรและถือว่าคุ้มค่าวัตถุกลับจะถูกต้องอยู่ในขอบเขตท้องถิ่น (จนถึงภายในสระว่ายน้ำเปิดตัวมากที่สุดคือการระบายน้ำมาประกอบกับ
ns_returns_autoreleased)
การเรียกmethodForSelector:สมมติว่าค่าส่งคืนของวิธีการที่เรียกเป็นวัตถุ แต่ไม่เก็บ / ปล่อยมัน ดังนั้นคุณสามารถสร้างการรั่วไหลได้หากวัตถุของคุณควรได้รับการปล่อยตัวตามที่กล่าวไว้ในข้อ 3 ข้างต้น (นั่นคือวิธีที่คุณใช้เรียกคืนวัตถุใหม่)
สำหรับตัวเลือกที่คุณพยายามโทรหาการส่งคืนนั้นvoidหรือวัตถุอื่น ๆ ที่ไม่ใช่คุณสามารถเปิดใช้งานคุณสมบัติคอมไพเลอร์เพื่อละเว้นคำเตือน แต่อาจเป็นอันตรายได้ ฉันเคยเห็นเสียงดังกราวไปทำซ้ำสองสามครั้งว่าวิธีจัดการกับค่าส่งคืนที่ไม่ได้กำหนดให้กับตัวแปรท้องถิ่น ไม่มีเหตุผลที่เปิดใช้งาน ARC ไม่สามารถเก็บและปล่อยค่าวัตถุที่ส่งคืนจากmethodForSelector:แม้ว่าคุณจะไม่ต้องการใช้ จากมุมมองของคอมไพเลอร์มันเป็นวัตถุหลังจากทั้งหมด ซึ่งหมายความว่าหากวิธีการที่คุณโทรกำลังsomeMethodส่งคืนวัตถุที่ไม่ใช่ (รวมถึงvoid) คุณสามารถจบลงด้วยค่าตัวชี้ขยะที่ถูกเก็บไว้ / ปล่อยและผิดพลาด
ข้อโต้แย้งเพิ่มเติม
สิ่งหนึ่งที่ควรพิจารณาคือนี่คือคำเตือนเดียวกันที่จะเกิดขึ้นperformSelector:withObject:และคุณสามารถพบปัญหาที่คล้ายกันโดยไม่ต้องประกาศว่าวิธีนั้นใช้พารามิเตอร์อย่างไร ARC อนุญาตให้ประกาศพารามิเตอร์ที่ใช้ไปและหากวิธีการนั้นใช้พารามิเตอร์คุณอาจส่งข้อความไปยังซอมบี้และในที่สุด มีวิธีแก้ไขปัญหานี้กับการคัดเลือกนักแสดงบริดจ์ แต่จริงๆแล้วมันจะดีกว่าถ้าใช้วิธีการIMPและตัวชี้ฟังก์ชันด้านบน เนื่องจากพารามิเตอร์ที่ใช้แล้วไม่ค่อยมีปัญหาจึงไม่น่าจะเกิดขึ้น
ตัวเลือกแบบคงที่
ที่น่าสนใจคอมไพเลอร์จะไม่บ่นเกี่ยวกับตัวเลือกที่ประกาศแบบคงที่:
[_controller performSelector:@selector(someMethod)];
เหตุผลนี้เป็นเพราะคอมไพเลอร์จริงสามารถบันทึกข้อมูลทั้งหมดเกี่ยวกับตัวเลือกและวัตถุในระหว่างการรวบรวม ไม่จำเป็นต้องตั้งสมมติฐานเกี่ยวกับอะไรเลย (ฉันตรวจสอบสิ่งนี้เป็นปีที่แล้วโดยดูจากแหล่งที่มา แต่ตอนนี้ยังไม่มีการอ้างอิง)
การปราบปราม
ในการพยายามนึกถึงสถานการณ์ที่การยับยั้งคำเตือนนี้จะมีความจำเป็นและการออกแบบรหัสที่ดีฉันกำลังจะว่างเปล่า บางคนได้โปรดแบ่งปันหากพวกเขามีประสบการณ์ในการปิดเสียงเตือนนี้เป็นสิ่งที่จำเป็น
มากกว่า
เป็นไปได้ที่จะสร้างขึ้นNSMethodInvocationเพื่อจัดการกับสิ่งนี้เช่นกัน แต่การทำเช่นนั้นต้องใช้การพิมพ์มากขึ้นและช้าลงดังนั้นจึงมีเหตุผลเล็กน้อยที่จะทำเช่นนั้น
ประวัติศาสตร์
เมื่อperformSelector:ตระกูลของวิธีการถูกเพิ่มลงใน Objective-C เป็นครั้งแรก ARC ไม่มีอยู่จริง ในขณะที่สร้าง ARC นั้น Apple ตัดสินใจว่าควรสร้างคำเตือนสำหรับวิธีการเหล่านี้เพื่อเป็นแนวทางในการแนะนำผู้พัฒนาไปสู่การใช้วิธีการอื่นเพื่อกำหนดวิธีจัดการหน่วยความจำอย่างชัดเจนเมื่อส่งข้อความโดยผ่านตัวเลือกที่กำหนด ใน Objective-C ผู้พัฒนาสามารถทำสิ่งนี้ได้โดยใช้การใช้งานลักษณะ C บนตัวชี้ฟังก์ชันดิบ
ด้วยการแนะนำของสวิฟท์, แอปเปิ้ลมีเอกสารperformSelector:ครอบครัวของวิธีการเป็น "ไม่ปลอดภัยโดยเนื้อแท้" และพวกเขาจะไม่สามารถใช้ได้กับสวิฟท์
เมื่อเวลาผ่านไปเราได้เห็นความก้าวหน้านี้:
- อนุญาตให้ Objective-C เวอร์ชันก่อนหน้า
performSelector:(การจัดการหน่วยความจำด้วยตนเอง)
- Objective-C กับ ARC เตือนให้ใช้
performSelector:
- Swift ไม่สามารถเข้าถึง
performSelector:และจัดทำเอกสารวิธีการเหล่านี้ในฐานะ "ไม่ปลอดภัยโดยเนื้อแท้"
แนวคิดของการส่งข้อความตามตัวเลือกที่ระบุชื่อนั้นไม่ใช่คุณสมบัติ "ไม่ปลอดภัยโดยเนื้อแท้" แนวคิดนี้ถูกนำมาใช้อย่างประสบความสำเร็จเป็นเวลานานใน Objective-C รวมถึงภาษาการเขียนโปรแกรมอื่น ๆ อีกมากมาย
1เมธอด Objective-C ทั้งหมดมีอาร์กิวเมนต์ที่ซ่อนอยู่สองข้อselfและ_cmdที่เพิ่มเข้ามาโดยนัยเมื่อคุณเรียกใช้เมธอด
2 การเรียกใช้NULLฟังก์ชั่นนั้นไม่ปลอดภัยใน C. ตัวป้องกันที่ใช้ในการตรวจสอบว่ามีตัวควบคุมอยู่หรือไม่เพื่อให้แน่ใจว่าเรามีวัตถุ ดังนั้นเราจึงรู้ว่าเราจะได้รับIMPจากmethodForSelector:(แม้ว่าอาจเป็นไปได้ที่จะ_objc_msgForwardเข้าสู่ระบบการส่งต่อข้อความ) โดยพื้นฐานแล้วด้วยยามที่เรารู้ว่าเรามีฟังก์ชั่นการโทร
3 ที่จริงแล้วอาจเป็นไปได้ที่จะได้รับข้อมูลที่ไม่ถูกต้องหากประกาศว่าคุณเป็นวัตถุidและคุณไม่ได้นำเข้าส่วนหัวทั้งหมด คุณสามารถจบลงด้วยการล่มในรหัสที่คอมไพเลอร์คิดว่าดี นี่เป็นของหายากมาก แต่อาจเกิดขึ้นได้ โดยปกติคุณจะได้รับคำเตือนว่าจะไม่ทราบว่าจะเลือกลายเซ็นวิธีใดจากสองวิธี
4ดูการอ้างอิง ARC เกี่ยวกับค่าส่งคืนที่เก็บไว้และค่าส่งคืนที่ไม่ได้รับสำหรับรายละเอียดเพิ่มเติม