สารละลาย
คอมไพเลอร์เตือนเกี่ยวกับเรื่องนี้ด้วยเหตุผล มันยากมากที่คำเตือนนี้ควรถูกเพิกเฉยและง่ายต่อการแก้ไข นี่คือวิธี:
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>
) เหล่านี้คำแนะนำการทำงานที่เรียกว่าIMP
s และมีความเรียบง่าย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
/ copy
family หรือประกอบกับ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 เกี่ยวกับค่าส่งคืนที่เก็บไว้และค่าส่งคืนที่ไม่ได้รับสำหรับรายละเอียดเพิ่มเติม