performSelector อาจทำให้เกิดการรั่วไหลเนื่องจากไม่รู้จักตัวเลือก


1258

ฉันได้รับคำเตือนต่อไปนี้โดยคอมไพเลอร์ ARC:

"performSelector may cause a leak because its selector is unknown".

นี่คือสิ่งที่ฉันทำ:

[_controller performSelector:NSSelectorFromString(@"someMethod")];

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


3
ชื่อของตัวแปรนั้นเป็นแบบไดนามิกมันขึ้นอยู่กับหลาย ๆ อย่าง มีความเสี่ยงที่ฉันเรียกสิ่งที่ไม่มีอยู่ แต่นั่นไม่ใช่ปัญหา
Eduardo Scoz

6
@matt เหตุใดการเรียกใช้เมธอดแบบไดนามิกบนวัตถุนั้นถือว่าเป็นการปฏิบัติที่ไม่ดี ไม่ใช่วัตถุประสงค์ทั้งหมดของ NSSelectorFromString () เพื่อสนับสนุนการปฏิบัตินี้หรือไม่?
Eduardo Scoz

7
คุณควร / สามารถทดสอบ [_controller การตอบสนองต่อ SelectSelector: mySelector] ก่อนที่จะทำการตั้งค่าผ่าน performSelector:
mattacular

50
@mattacular หวังว่าฉันจะลงคะแนน: "นั่นคือการปฏิบัติที่ไม่ดี"
ctpenrose

6
หากคุณรู้ว่าสตริงเป็นตัวอักษรให้ใช้ @selector () เพื่อให้คอมไพเลอร์สามารถบอกได้ว่าชื่อตัวเลือกคืออะไร หากรหัสจริงของคุณกำลังเรียก NSSelectorFromString () ด้วยสตริงที่สร้างขึ้นหรือให้ที่รันไทม์คุณต้องใช้ NSSelectorFromString ()
Chris หน้า

คำตอบ:


1211

สารละลาย

คอมไพเลอร์เตือนเกี่ยวกับเรื่องนี้ด้วยเหตุผล มันยากมากที่คำเตือนนี้ควรถูกเพิกเฉยและง่ายต่อการแก้ไข นี่คือวิธี:

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

  1. ประเภทไม่สนใจไม่ใช่วัตถุ ( void, intฯลฯ )
  2. เก็บค่าของออบเจ็กต์จากนั้นปล่อยเมื่อไม่ใช้งานอีกต่อไป (ข้อสมมติฐานมาตรฐาน)
  3. ปล่อยค่าออบเจคใหม่เมื่อไม่ใช้งานอีกต่อไป (วิธีการในinit/ copyfamily หรือประกอบกับns_returns_retained)
  4. ทำอะไรและถือว่าคุ้มค่าวัตถุกลับจะถูกต้องอยู่ในขอบเขตท้องถิ่น (จนถึงภายในสระว่ายน้ำเปิดตัวมากที่สุดคือการระบายน้ำมาประกอบกับ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:ครอบครัวของวิธีการเป็น "ไม่ปลอดภัยโดยเนื้อแท้" และพวกเขาจะไม่สามารถใช้ได้กับสวิฟท์

เมื่อเวลาผ่านไปเราได้เห็นความก้าวหน้านี้:

  1. อนุญาตให้ Objective-C เวอร์ชันก่อนหน้าperformSelector:(การจัดการหน่วยความจำด้วยตนเอง)
  2. Objective-C กับ ARC เตือนให้ใช้ performSelector:
  3. Swift ไม่สามารถเข้าถึงperformSelector:และจัดทำเอกสารวิธีการเหล่านี้ในฐานะ "ไม่ปลอดภัยโดยเนื้อแท้"

แนวคิดของการส่งข้อความตามตัวเลือกที่ระบุชื่อนั้นไม่ใช่คุณสมบัติ "ไม่ปลอดภัยโดยเนื้อแท้" แนวคิดนี้ถูกนำมาใช้อย่างประสบความสำเร็จเป็นเวลานานใน Objective-C รวมถึงภาษาการเขียนโปรแกรมอื่น ๆ อีกมากมาย


1เมธอด Objective-C ทั้งหมดมีอาร์กิวเมนต์ที่ซ่อนอยู่สองข้อselfและ_cmdที่เพิ่มเข้ามาโดยนัยเมื่อคุณเรียกใช้เมธอด

2 การเรียกใช้NULLฟังก์ชั่นนั้นไม่ปลอดภัยใน C. ตัวป้องกันที่ใช้ในการตรวจสอบว่ามีตัวควบคุมอยู่หรือไม่เพื่อให้แน่ใจว่าเรามีวัตถุ ดังนั้นเราจึงรู้ว่าเราจะได้รับIMPจากmethodForSelector:(แม้ว่าอาจเป็นไปได้ที่จะ_objc_msgForwardเข้าสู่ระบบการส่งต่อข้อความ) โดยพื้นฐานแล้วด้วยยามที่เรารู้ว่าเรามีฟังก์ชั่นการโทร

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

4ดูการอ้างอิง ARC เกี่ยวกับค่าส่งคืนที่เก็บไว้และค่าส่งคืนที่ไม่ได้รับสำหรับรายละเอียดเพิ่มเติม


@wbyoung หากรหัสของคุณแก้ปัญหาการเก็บฉันสงสัยว่าทำไมperformSelector:วิธีการไม่ได้ดำเนินการด้วยวิธีนี้ พวกเขามีลายเซ็นวิธีการที่เข้มงวด (กลับมาidรับหนึ่งหรือสองid) ดังนั้นจึงไม่จำเป็นต้องจัดการประเภทดั้งเดิม
Tricertops

1
@Andy อาร์กิวเมนต์ได้รับการจัดการตามความหมายของต้นแบบของวิธีการ (มันจะไม่ถูกเก็บไว้ / เผยแพร่) ความกังวลส่วนใหญ่จะขึ้นอยู่กับประเภทผลตอบแทน
wbyoung

2
"ตัวอย่างที่ซับซ้อน" ให้ข้อผิดพลาดCannot initialize a variable of type 'CGRect (*)(__strong id, SEL, CGRect, UIView *__strong)' with an rvalue of type 'void *'เมื่อใช้ Xcode ล่าสุด (5.1.1) ถึงกระนั้นฉันได้เรียนรู้มากมาย!
Stan James

2
void (*func)(id, SEL) = (void *)imp;ไม่ได้คอมไพล์ฉันได้แทนที่ด้วยvoid (*func)(id, SEL) = (void (*)(id, SEL))imp;
Davyd Geyl

1
เปลี่ยนvoid (*func)(id, SEL) = (void *)imp;เป็น<…> = (void (*))imp;หรือ<…> = (void (*) (id, SEL))imp;
Isaak Osipovich Dunayevsky

1182

ในคอมไพเลอร์ LLVM 3.0 ใน Xcode 4.2 คุณสามารถระงับคำเตือนดังต่อไปนี้:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self.ticketTarget performSelector: self.ticketAction withObject: self];
#pragma clang diagnostic pop

หากคุณได้รับข้อผิดพลาดในหลาย ๆ ที่และต้องการใช้ระบบมาโคร C เพื่อซ่อน pragmas คุณสามารถกำหนดแมโครเพื่อให้ง่ายต่อการระงับคำเตือน:

#define SuppressPerformSelectorLeakWarning(Stuff) \
    do { \
        _Pragma("clang diagnostic push") \
        _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
        Stuff; \
        _Pragma("clang diagnostic pop") \
    } while (0)

คุณสามารถใช้แมโครดังนี้:

SuppressPerformSelectorLeakWarning(
    [_target performSelector:_action withObject:self]
);

หากคุณต้องการผลลัพธ์ของข้อความที่ดำเนินการคุณสามารถทำได้:

id result;
SuppressPerformSelectorLeakWarning(
    result = [_target performSelector:_action withObject:self]
);

วิธีนี้อาจทำให้หน่วยความจำรั่วเมื่อปรับการปรับให้เหมาะสมเป็นอย่างอื่นนอกจากไม่มี
Eric

4
@Eric ไม่สามารถทำได้ยกเว้นว่าคุณกำลังใช้วิธีตลก ๆ เช่น "initSomething" หรือ "newSomething" หรือ "somethingCopy"
Andrey Tarantsov

3
@ จูเลียนนั่นใช้งานได้ แต่มันปิดการเตือนสำหรับไฟล์ทั้งหมด - คุณอาจไม่ต้องการหรือต้องการมัน การห่อด้วยมันpopและpush-pragmas นั้นสะอาดกว่าและปลอดภัยกว่ามาก
Emil

2
ทั้งหมดนี้ก็คือมันทำให้คอมไพเลอร์เงียบขึ้น นี่ไม่ได้แก้ปัญหา หากไม่มีตัวเลือกอยู่แสดงว่าคุณเมามาก
Andra Todorescu

2
สิ่งนี้ควรใช้เมื่อห่อด้วยif ([_target respondsToSelector:_selector]) {ตรรกะหรือสิ่งที่คล้ายกัน

208

ฉันเดาว่านี่คือสิ่งนี้: เนื่องจากตัวเลือกไม่รู้จักคอมไพเลอร์ ARC จึงไม่สามารถบังคับใช้การจัดการหน่วยความจำที่เหมาะสม

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

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


5
ขอบคุณสำหรับคำตอบฉันจะดูสิ่งนี้มากขึ้นเพื่อดูว่าเกิดอะไรขึ้น มีความคิดเกี่ยวกับวิธีที่ฉันสามารถข้ามคำเตือนและทำให้มันหายไปได้อย่างไร ฉันจะเกลียดที่จะมีคำเตือนนั่งอยู่ในรหัสของฉันตลอดไปสำหรับสิ่งที่โทรปลอดภัย
Eduardo Scoz

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

5
คำตอบนี้ก่อให้เกิดคำถามบางอย่างเช่นถ้า ARC พยายามหาค่าเมื่อปล่อยบางสิ่งบางอย่างตามชื่อการประชุมและเมธอดแล้ว "การนับการอ้างอิง" เป็นอย่างไร พฤติกรรมที่คุณอธิบายฟังนั้นมีขอบเขตที่ดีกว่าโดยพลการอย่างสมบูรณ์ถ้า ARC กำลังสมมติว่ารหัสเป็นไปตามอนุสัญญาบางประการซึ่งตรงกันข้ามกับการติดตามการอ้างอิงจริงไม่ว่าจะมีการปฏิบัติตามอนุสัญญาใด
aroth

8
ARC ดำเนินการโดยอัตโนมัติในการเพิ่มโพสต์และรีลีสที่คอมไพล์ ไม่ใช่การรวบรวมขยะ (ซึ่งเป็นเหตุผลว่าทำไมจึงรวดเร็วและเหนือศีรษะอย่างไม่น่าเชื่อ) มันไม่ได้โดยพลการเลย กฎเริ่มต้นขึ้นอยู่กับการประชุม ObjC ที่ได้รับการยอมรับอย่างดี นี่เป็นการหลีกเลี่ยงความจำเป็นในการเพิ่ม__attributeวิธีการอธิบายการจัดการหน่วยความจำอย่างชัดเจน แต่มันก็ทำให้เป็นไปไม่ได้ที่ผู้ complier จะจัดการรูปแบบนี้อย่างถูกต้อง (รูปแบบที่เคยเป็นเรื่องธรรมดามาก แต่ถูกแทนที่ด้วยรูปแบบที่แข็งแกร่งขึ้นในช่วงไม่กี่ปีที่ผ่านมา)
Rob Napier

8
ดังนั้นเราไม่สามารถมี ivar ประเภทSELและกำหนดตัวเลือกที่แตกต่างกันตามสถานการณ์ได้อีกต่อไป? วิธีที่จะไปภาษาแบบไดนามิก ...
นิโคลัส Miari

121

ในโครงการของคุณตั้งค่ารูปร่างภายใต้ธงคำเตือนอื่น ๆ ( WARNING_CFLAGS), เพิ่ม
-Wno-arc-performSelector-leaks

ตอนนี้ให้แน่ใจว่าตัวเลือกที่คุณโทรไม่ทำให้วัตถุของคุณถูกเก็บหรือคัดลอก


12
หมายเหตุคุณสามารถเพิ่มการตั้งค่าสถานะเดียวกันสำหรับไฟล์ที่เฉพาะเจาะจงมากกว่าโครงการทั้งหมด หากคุณดูที่ Build Phases-> Compile Sources คุณสามารถตั้งค่าต่อไฟล์คอมไพเลอร์แฟล็ก (เช่นเดียวกับที่คุณต้องการยกเว้นไฟล์จาก ARC) ในโครงการของฉันมีเพียงไฟล์เดียวที่ควรใช้ตัวเลือกด้วยวิธีนี้ดังนั้นฉันจึงแยกมันออกแล้วปล่อยไฟล์อื่น
Michael

111

เป็นวิธีแก้ปัญหาจนกว่าคอมไพเลอร์อนุญาตให้แทนที่คำเตือนคุณสามารถใช้รันไทม์

objc_msgSend(_controller, NSSelectorFromString(@"someMethod"));

แทน

[_controller performSelector:NSSelectorFromString(@"someMethod")];

คุณจะต้อง

#import <objc/message.h>


8
ARC รับรองอนุสัญญาโกโก้แล้วเพิ่มการรักษาและเผยแพร่ตามอนุสัญญาเหล่านั้น เนื่องจาก C ไม่ปฏิบัติตามอนุสัญญาเหล่านั้น ARC จึงบังคับให้คุณใช้เทคนิคการจัดการหน่วยความจำด้วยตนเอง หากคุณสร้างวัตถุ CF คุณต้อง CFRelease () วัตถุนั้น หากคุณ dispatch_queue_create () คุณต้อง dispatch_release () บรรทัดล่างหากคุณต้องการหลีกเลี่ยงคำเตือน ARC คุณสามารถหลีกเลี่ยงได้โดยใช้วัตถุ C และการจัดการหน่วยความจำด้วยตนเอง นอกจากนี้คุณสามารถปิดใช้งาน ARC บนพื้นฐานสำหรับแต่ละไฟล์โดยใช้แฟล็กคอมไพเลอร์ -fno-objc-arc บนไฟล์นั้น
jluckyiv

8
ไม่ได้โดยไม่ต้องหล่อคุณไม่สามารถ Varargs ไม่เหมือนกับรายการอาร์กิวเมนต์ที่พิมพ์อย่างชัดเจน โดยทั่วไปจะทำงานโดยบังเอิญ แต่ฉันไม่คิดว่า "บังเอิญ" ถูกต้อง
bbum

21
อย่าทำอย่างนั้น[_controller performSelector:NSSelectorFromString(@"someMethod")];และobjc_msgSend(_controller, NSSelectorFromString(@"someMethod"));ไม่เทียบเท่า! ลองดูวิธีการที่ลายเซ็นไม่ตรงกันและจุดอ่อนใหญ่ในการพิมพ์ที่อ่อนแอของ Objective-Cพวกเขาอธิบายปัญหาในเชิงลึก
0xced

5
@ 0xced ในกรณีนี้มันใช้ได้ objc_msgSend จะไม่สร้างลายเซ็นเมธอดที่ไม่ตรงกันสำหรับตัวเลือกใด ๆ ที่จะทำงานได้อย่างถูกต้องใน performSelector: หรือตัวแปรต่างๆเนื่องจากพวกเขาใช้วัตถุเป็นพารามิเตอร์เท่านั้น ตราบใดที่พารามิเตอร์ทั้งหมดของคุณเป็นตัวชี้ (รวมถึงวัตถุ), double และ NSInteger / long และประเภทการคืนของคุณเป็นโมฆะตัวชี้หรือความยาวแล้ว objc_msgSend จะทำงานได้อย่างถูกต้อง
Matt Gallagher

88

หากต้องการละเว้นข้อผิดพลาดเฉพาะในไฟล์ที่มีตัวเลือกการดำเนินการให้เพิ่ม #pragma ดังนี้:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

สิ่งนี้จะเพิกเฉยต่อคำเตือนในบรรทัดนี้ แต่ก็ยังอนุญาตให้ใช้ได้ตลอดทั้งโครงการที่เหลือของคุณ


6
#pragma clang diagnostic warning "-Warc-performSelector-leaks"ผมเข้าใจว่าคุณยังสามารถหันหลังกลับคำเตือนในทันทีหลังจากที่วิธีการในคำถามด้วย ฉันรู้ว่าถ้าฉันปิดคำเตือนฉันชอบที่จะเปิดอีกครั้งในช่วงเวลาที่เป็นไปได้ที่เร็วที่สุดดังนั้นฉันจึงไม่ได้ตั้งใจให้ใบเตือนที่ไม่คาดคิดอีกใบ ไม่น่าเป็นไปได้ว่านี่จะเป็นปัญหา แต่เป็นเพียงการฝึกฝนของฉันเมื่อใดก็ตามที่ฉันปิดการเตือน
Rob

2
คุณยังสามารถเรียกคืนสถานะการกำหนดค่าคอมไพเลอร์ก่อนหน้าของคุณโดยใช้#pragma clang diagnostic warning pushก่อนที่จะทำการเปลี่ยนแปลงใด ๆ และ#pragma clang diagnostic warning popเพื่อเรียกคืนสถานะก่อนหน้า มีประโยชน์หากคุณกำลังปิดการโหลดและไม่ต้องการเปิดใช้งานบรรทัด pragma อีกครั้งในรหัสของคุณ
deanWombourne

มันจะไม่สนใจบรรทัดต่อไปนี้เท่านั้น?
hfossli

70

แปลก แต่จริง: ถ้ายอมรับได้ (เช่นผลลัพธ์เป็นโมฆะและคุณไม่รังเกียจที่จะให้วงจร runloop หนึ่งครั้ง) เพิ่มความล่าช้าแม้ว่าจะเป็นศูนย์:

[_controller performSelector:NSSelectorFromString(@"someMethod")
    withObject:nil
    afterDelay:0];

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


2
คุณรู้หรือไม่ว่านี่จะช่วยแก้ไขปัญหาการจัดการหน่วยความจำที่เกี่ยวข้องจริง ๆ หรือมีปัญหาเดียวกัน แต่ Xcode ไม่ฉลาดพอที่จะเตือนคุณด้วยรหัสนี้?
Aaron Brager

นี่คือความหมายไม่เหมือนกัน! ใช้ performSelector: withObject: AfterDelay: จะทำการเลือกในการรัน runloop ครั้งต่อไป ดังนั้นวิธีนี้จะส่งคืนทันที
Florian

10
@ Florian แน่นอนว่ามันไม่เหมือนกัน! อ่านคำตอบของฉัน: ฉันบอกว่าถ้ายอมรับได้เพราะผลที่ได้คือโมฆะและรอบ runloop นั่นเป็นประโยคแรกของคำตอบของฉัน
แมตต์

34

นี่คือมาโครที่อัปเดตตามคำตอบที่ระบุไว้ด้านบน อันนี้ควรอนุญาตให้คุณล้อมโค้ดของคุณแม้ว่าจะมีคำสั่ง return

#define SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(code)                        \
    _Pragma("clang diagnostic push")                                        \
    _Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"")     \
    code;                                                                   \
    _Pragma("clang diagnostic pop")                                         \


SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING(
    return [_target performSelector:_action withObject:self]
);

6
returnไม่จำเป็นต้องอยู่ภายในมาโคร return SUPPRESS_PERFORM_SELECTOR_LEAK_WARNING([_target performSelector:_action withObject:self]);ยังใช้งานได้และดู saner
uasi

31

รหัสนี้ไม่เกี่ยวข้องกับการตั้งค่าสถานะคอมไพเลอร์หรือโทรโดยตรงรันไทม์:

SEL selector = @selector(zeroArgumentMethod);
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];

NSInvocation อนุญาตให้ตั้งค่าอาร์กิวเมนต์หลายรายการซึ่งแตกต่างจาก performSelectorวิธีนี้ในวิธีการใด ๆ


3
คุณรู้หรือไม่ว่านี่จะช่วยแก้ไขปัญหาการจัดการหน่วยความจำที่เกี่ยวข้องจริง ๆ หรือมีปัญหาเดียวกัน แต่ Xcode ไม่ฉลาดพอที่จะเตือนคุณด้วยรหัสนี้?
Aaron Brager

1
คุณสามารถพูดได้ว่ามันแก้ปัญหาการจัดการหน่วยความจำ แต่นี่เป็นเพราะมันช่วยให้คุณสามารถระบุพฤติกรรมได้ ตัวอย่างเช่นคุณสามารถเลือกที่จะให้การภาวนารักษาข้อโต้แย้งได้หรือไม่ สำหรับความรู้ปัจจุบันของฉันมันพยายามแก้ไขปัญหาลายเซ็นที่ไม่ตรงกันซึ่งอาจปรากฏขึ้นโดยเชื่อใจว่าคุณรู้ว่าคุณกำลังทำอะไรและไม่ได้ให้ข้อมูลที่ไม่ถูกต้อง ฉันไม่แน่ใจว่าการตรวจสอบทั้งหมดสามารถทำได้ที่รันไทม์หรือไม่ ดังที่ได้กล่าวถึงในความคิดเห็นอื่นmikeash.com/pyblog/…อธิบายอย่างชัดเจนว่าสิ่งที่ไม่ตรงกันสามารถทำได้
หมดเวลา Timar

20

มีคำตอบมากมายที่นี่ แต่เนื่องจากนี่แตกต่างกันเล็กน้อยรวมคำตอบสองสามข้อที่ฉันคิดว่าฉันใส่ไว้ฉันใช้หมวดหมู่ NSObject ซึ่งตรวจสอบเพื่อให้แน่ใจว่าตัวเลือกคืนค่าเป็นโมฆะและยังยับยั้งคอมไพเลอร์ คำเตือน.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import "Debug.h" // not given; just an assert

@interface NSObject (Extras)

// Enforce the rule that the selector used must return void.
- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object;
- (void) performVoidReturnSelector:(SEL)aSelector;

@end

@implementation NSObject (Extras)

// Apparently the reason the regular performSelect gives a compile time warning is that the system doesn't know the return type. I'm going to (a) make sure that the return type is void, and (b) disable this warning
// See http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown

- (void) checkSelector:(SEL)aSelector {
    // See http://stackoverflow.com/questions/14602854/objective-c-is-there-a-way-to-check-a-selector-return-value
    Method m = class_getInstanceMethod([self class], aSelector);
    char type[128];
    method_getReturnType(m, type, sizeof(type));

    NSString *message = [[NSString alloc] initWithFormat:@"NSObject+Extras.performVoidReturnSelector: %@.%@ selector (type: %s)", [self class], NSStringFromSelector(aSelector), type];
    NSLog(@"%@", message);

    if (type[0] != 'v') {
        message = [[NSString alloc] initWithFormat:@"%@ was not void", message];
        [Debug assertTrue:FALSE withMessage:message];
    }
}

- (void) performVoidReturnSelector:(SEL)aSelector withObject:(id)object {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    // Since the selector (aSelector) is returning void, it doesn't make sense to try to obtain the return result of performSelector. In fact, if we do, it crashes the app.
    [self performSelector: aSelector withObject: object];
#pragma clang diagnostic pop    
}

- (void) performVoidReturnSelector:(SEL)aSelector {
    [self checkSelector:aSelector];

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [self performSelector: aSelector];
#pragma clang diagnostic pop
}

@end

ควรแทนที่ 'v' ด้วย _C_VOID หรือไม่ _C_VOID มีการประกาศใน <objc / runtime.h>
Rik Renich

16

เพื่อลูกหลานของฉันฉันตัดสินใจที่จะโยนหมวกของฉันลงในแหวน :)

เมื่อเร็ว ๆ นี้ฉันได้เห็นการปรับโครงสร้างมากขึ้นเรื่อย ๆ จากtarget/ selectorกระบวนทัศน์เพื่อสนับสนุนสิ่งต่าง ๆ เช่นโพรโทคอลบล็อก ฯลฯ อย่างไรก็ตามมีการแทนที่แบบหล่นลงหนึ่งperformSelectorที่ฉันใช้ไม่กี่ครั้ง:

[NSApp sendAction: NSSelectorFromString(@"someMethod") to: _controller from: nil];

เหล่านี้ดูเหมือนจะสะอาด ARC ปลอดภัยและเกือบจะเหมือนกับแทนโดยไม่ต้องมากเกี่ยวกับperformSelectorobjc_msgSend()

แม้ว่าฉันจะไม่รู้ว่ามีอะนาล็อกใน iOS


6
ขอขอบคุณสำหรับการรวมนี้ .. มันมีอยู่ใน [[UIApplication sharedApplication] sendAction: to: from: forEvent:]iOS: ฉันดูมันครั้งเดียว แต่รู้สึกอึดอัดใจที่จะใช้คลาสที่เกี่ยวข้องกับ UI ตรงกลางโดเมนหรือบริการของคุณเพื่อทำการโทรแบบไดนามิก .. ขอบคุณที่รวมสิ่งนี้ไว้ด้วย!
Eduardo Scoz

2
Ew! มันจะมีค่าใช้จ่ายมากขึ้น (เนื่องจากต้องตรวจสอบว่าวิธีการนั้นมีให้ใช้งานหรือไม่และเดินขึ้นห่วงโซ่ผู้ตอบกลับหากยังไม่ได้) และมีพฤติกรรมข้อผิดพลาดที่แตกต่างกัน (การเดินขึ้นห่วงโซ่ผู้ตอบกลับ ซึ่งตอบสนองต่อวิธีการแทนที่จะหยุดทำเพียง) มันยังไม่ทำงานเมื่อคุณต้องการidจาก-performSelector:...
tc

2
@tc มันไม่ได้ "เดินหน้าต่อห่วงโซ่การตอบกลับ" เว้นแต่to:จะเป็นศูนย์ซึ่งไม่ใช่ มันจะตรงไปยังวัตถุเป้าหมายโดยไม่มีการตรวจสอบล่วงหน้า ดังนั้นจึงไม่มี "ค่าใช้จ่ายเพิ่มเติม" มันไม่ใช่ทางออกที่ดี แต่เหตุผลที่คุณให้ไม่ใช่เหตุผล :)
แมตต์

15

คำตอบของ Matt Galloway ในหัวข้อนี้อธิบายถึงสาเหตุ:

พิจารณาสิ่งต่อไปนี้:

id anotherObject1 = [someObject performSelector:@selector(copy)];
id anotherObject2 = [someObject performSelector:@selector(giveMeAnotherNonRetainedObject)];

ทีนี้ ARC จะรู้ได้อย่างไรว่าอันแรกส่งคืนอ๊อบเจ๊กต์ที่มีจำนวนนับคงที่ 1 แต่อันที่สองส่งคืนออบเจ็กต์ที่ถูกลบอัตโนมัติ

ดูเหมือนว่าโดยทั่วไปจะปลอดภัยที่จะระงับคำเตือนหากคุณไม่สนใจค่าส่งคืน ฉันไม่แน่ใจว่าวิธีปฏิบัติที่ดีที่สุดคืออะไรถ้าคุณต้องการรับวัตถุที่เก็บไว้จากตัวเลือกตัวเลือก - นอกจาก "อย่าทำอย่างนั้น"


14

@ C-ถนนให้การเชื่อมโยงทางด้านขวาที่มีคำอธิบายปัญหาที่นี่ ด้านล่างคุณสามารถดูตัวอย่างของฉันเมื่อ performerelector ทำให้หน่วยความจำรั่ว

@interface Dummy : NSObject <NSCopying>
@end

@implementation Dummy

- (id)copyWithZone:(NSZone *)zone {
  return [[Dummy alloc] init];
}

- (id)clone {
  return [[Dummy alloc] init];
}

@end

void CopyDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy copy];
}

void CloneDummy(Dummy *dummy) {
  __unused Dummy *dummyClone = [dummy clone];
}

void CopyDummyWithLeak(Dummy *dummy, SEL copySelector) {
  __unused Dummy *dummyClone = [dummy performSelector:copySelector];
}

void CloneDummyWithoutLeak(Dummy *dummy, SEL cloneSelector) {
  __unused Dummy *dummyClone = [dummy performSelector:cloneSelector];
}

int main(int argc, const char * argv[]) {
  @autoreleasepool {
    Dummy *dummy = [[Dummy alloc] init];
    for (;;) { @autoreleasepool {
      //CopyDummy(dummy);
      //CloneDummy(dummy);
      //CloneDummyWithoutLeak(dummy, @selector(clone));
      CopyDummyWithLeak(dummy, @selector(copy));
      [NSThread sleepForTimeInterval:1];
    }} 
  }
  return 0;
}

วิธีเดียวที่ทำให้หน่วยความจำรั่วในตัวอย่างของฉันคือ CopyDummyWithLeak เหตุผลก็คือ ARC ไม่ทราบว่า copySelector ส่งคืนวัตถุที่ถูกเก็บไว้

หากคุณเรียกใช้ Memory Leak Tool คุณจะเห็นภาพต่อไปนี้: ป้อนคำอธิบายรูปภาพที่นี่ ... และไม่มีการรั่วไหลของหน่วยความจำในกรณีอื่น ๆ : ป้อนคำอธิบายรูปภาพที่นี่


6

เพื่อทำให้มาโครของ Scott Thompson เป็นเรื่องทั่วไปมากขึ้น:

// String expander
#define MY_STRX(X) #X
#define MY_STR(X) MY_STRX(X)

#define MYSilenceWarning(FLAG, MACRO) \
_Pragma("clang diagnostic push") \
_Pragma(MY_STR(clang diagnostic ignored MY_STR(FLAG))) \
MACRO \
_Pragma("clang diagnostic pop")

จากนั้นใช้แบบนี้:

MYSilenceWarning(-Warc-performSelector-leaks,
[_target performSelector:_action withObject:self];
                )

FWIW ฉันไม่ได้เพิ่มมาโคร มีคนเพิ่มว่าการตอบสนองของฉัน ส่วนตัวแล้วฉันจะไม่ใช้มาโคร pragma อยู่ที่นั่นเพื่อแก้ไขกรณีพิเศษในรหัสและ pragmas นั้นชัดเจนและตรงไปตรงมาเกี่ยวกับสิ่งที่เกิดขึ้น ฉันชอบที่จะให้พวกเขาอยู่ในสถานที่มากกว่าที่จะซ่อนหรือทำให้พวกเขาอยู่เบื้องหลังแมโคร แต่นั่นเป็นเพียงฉัน YMMV
Scott Thompson

@ScottThompson มันยุติธรรม สำหรับฉันมันง่ายที่จะค้นหาแมโครนี้ในฐานรหัสของฉันและโดยทั่วไปแล้วฉันยังเพิ่มคำเตือนที่ไม่มีการปิดเสียงเพื่อจัดการกับปัญหาพื้นฐาน
Ben Flynn

6

อย่าระงับคำเตือน!

มีทางเลือกไม่น้อยกว่า12วิธีในการแก้ไขปัญหาด้วยคอมไพเลอร์
ในขณะที่คุณฉลาดในขณะที่มีการใช้งานครั้งแรกวิศวกรเพียงไม่กี่คนบนโลกสามารถติดตามรอยเท้าของคุณและรหัสนี้จะแตกในที่สุด

เส้นทางที่ปลอดภัย:

โซลูชันทั้งหมดเหล่านี้จะทำงานได้โดยมีระดับความผันแปรตามเจตนาดั้งเดิมของคุณ สมมติว่าparamเป็นได้nilถ้าคุณต้องการ:

เส้นทางที่ปลอดภัยพฤติกรรมที่เป็นแนวคิดเดียวกัน:

// GREAT
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:YES modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

เส้นทางที่ปลอดภัยพฤติกรรมที่แตกต่างกันเล็กน้อย:

(ดูที่นี้การตอบสนอง)
ใช้กระทู้ใด ๆ [NSThread mainThread]แทน

// GOOD
[_controller performSelector:selector withObject:anArgument afterDelay:0];
[_controller performSelector:selector withObject:anArgument afterDelay:0 inModes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO];
[_controller performSelectorOnMainThread:selector withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

[_controller performSelectorInBackground:selector withObject:anArgument];

[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO];
[_controller performSelector:selector onThread:[NSThread mainThread] withObject:anArgument waitUntilDone:NO modes:@[(__bridge NSString *)kCFRunLoopDefaultMode]];

เส้นทางอันตราย

ต้องใช้การคอมไพล์เลอร์แบบเงียบ ๆ ซึ่งถูกทำลาย ทราบว่าในช่วงเวลาปัจจุบันก็ไม่ทำลายในสวิฟท์

// AT YOUR OWN RISK
[_controller performSelector:selector];
[_controller performSelector:selector withObject:anArgument];
[_controller performSelector:selector withObject:anArgument withObject:nil];

3
การใช้ถ้อยคำผิดมาก เส้นทางที่ปลอดภัยไม่ปลอดภัยกว่าอันตรายเลย มันเป็นอันตรายมากกว่าเนื้อหาเพราะมันซ่อนคำเตือนโดยนัย
ไบรอันเฉิน

ฉันจะแก้ไขข้อความให้ไม่ดูถูก แต่ฉันยืนตามคำพูดของฉัน ครั้งเดียวที่ฉันพบว่าการเตือนการห้ามไม่ให้พูดคือถ้าฉันไม่ได้เป็นเจ้าของรหัส วิศวกรไม่สามารถรักษารหัสเงียบได้อย่างปลอดภัยโดยไม่เข้าใจผลลัพธ์ทั้งหมดซึ่งหมายความว่าอ่านอาร์กิวเมนต์นี้และการปฏิบัตินี้มีความเสี่ยง โดยเฉพาะอย่างยิ่งถ้าคุณพิจารณาทางเลือกที่ 12 ภาษาอังกฤษธรรมดาทางเลือกที่แข็งแกร่ง
SwiftArchitect

1
ไม่คุณไม่ได้รับคะแนนของฉัน ใช้performSelectorOnMainThreadเป็นที่ไม่ได้เป็นวิธีที่ดีที่จะเงียบเตือนและมีผลข้างเคียง (มันไม่ได้แก้ปัญหาการรั่วไหลของหน่วยความจำ) การ#clang diagnostic ignored ปราบปรามพิเศษเตือนอย่างชัดเจนในทางที่ชัดเจนมาก
ไบรอันเฉิ

จริงที่การเลือกตัวเลือกบนไม่ใช่- (void)วิธีเป็นปัญหาจริง
SwiftArchitect

และคุณจะเรียกตัวเลือกที่มีอาร์กิวเมนต์หลายตัวผ่านทางนี้ได้อย่างไรและปลอดภัยในเวลาเดียวกัน @SwiftArchitect
Catalin

4

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


ที่จริงแล้วบล็อกทำให้ง่ายต่อการสร้างวงจรการเก็บรักษาโดยไม่ตั้งใจซึ่ง ARC ไม่สามารถแก้ไขได้ ฉันยังหวังว่าจะมีคำเตือนของคอมไพเลอร์เมื่อคุณใช้โดยนัยselfผ่าน ivar (เช่นivarแทนself->ivar)
tc

คุณหมายถึงเช่น -Wimplicit-keep-self
OrangeDog

2

แทนที่จะใช้วิธีบล็อกซึ่งทำให้ฉันมีปัญหา:

    IMP imp = [_controller methodForSelector:selector];
    void (*func)(id, SEL) = (void *)imp;

ฉันจะใช้ NSInvocation แบบนี้:

    -(void) sendSelectorToDelegate:(SEL) selector withSender:(UIButton *)button 

    if ([delegate respondsToSelector:selector])
    {
    NSMethodSignature * methodSignature = [[delegate class]
                                    instanceMethodSignatureForSelector:selector];
    NSInvocation * delegateInvocation = [NSInvocation
                                   invocationWithMethodSignature:methodSignature];


    [delegateInvocation setSelector:selector];
    [delegateInvocation setTarget:delegate];

    // remember the first two parameter are cmd and self
    [delegateInvocation setArgument:&button atIndex:2];
    [delegateInvocation invoke];
    }

1

ถ้าคุณไม่จำเป็นต้องผ่านการขัดแย้งใด ๆ valueForKeyPathวิธีแก้ปัญหาที่ง่ายคือการใช้งาน สิ่งนี้สามารถเกิดขึ้นได้กับClassวัตถุ

NSString *colorName = @"brightPinkColor";
id uicolor = [UIColor class];
if ([uicolor respondsToSelector:NSSelectorFromString(colorName)]){
    UIColor *brightPink = [uicolor valueForKeyPath:colorName];
    ...
}

-2

คุณสามารถใช้โปรโตคอลได้ที่นี่ ดังนั้นสร้างโปรโตคอลอย่างเช่น:

@protocol MyProtocol
-(void)doSomethingWithObject:(id)object;
@end

ในชั้นเรียนของคุณที่ต้องโทรหาตัวเลือกของคุณคุณจะมี @property

@interface MyObject
    @property (strong) id<MyProtocol> source;
@end

เมื่อคุณต้องการโทร@selector(doSomethingWithObject:)ใน MyObject ให้ทำดังนี้

[self.source doSomethingWithObject:object];

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