iOS - วิธีใช้ performSelector ที่มีหลายอาร์กิวเมนต์และด้วย afterDelay?


90

ฉันเป็นมือใหม่ iOS ฉันมีวิธีการเลือกดังนี้ -

- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

ฉันกำลังพยายามใช้สิ่งนี้ -

[self performSelector:@selector(fooFirstInput:secondInput:) withObject:@"first" withObject:@"second" afterDelay:15.0];

แต่นั่นทำให้ฉันมีข้อผิดพลาดว่า -

Instance method -performSelector:withObject:withObject:afterDelay: not found

มีความคิดเกี่ยวกับสิ่งที่ฉันขาดหายไปหรือไม่?

คำตอบ:


143

โดยส่วนตัวแล้วฉันคิดว่าวิธีแก้ปัญหาที่ใกล้เคียงกับความต้องการของคุณคือการใช้ NSInvocation

สิ่งต่อไปนี้จะทำงาน:

indexPathและ dataSourceเป็นตัวแปรอินสแตนซ์สองตัวที่กำหนดด้วยวิธีการเดียวกัน

SEL aSelector = NSSelectorFromString(@"dropDownSelectedRow:withDataSource:");

if([dropDownDelegate respondsToSelector:aSelector]) {
    NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[dropDownDelegate methodSignatureForSelector:aSelector]];
    [inv setSelector:aSelector];
    [inv setTarget:dropDownDelegate];

    [inv setArgument:&(indexPath) atIndex:2]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
    [inv setArgument:&(dataSource) atIndex:3]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation

    [inv invoke];
}

2
เห็นด้วย มันควรจะเป็นคำตอบที่ถูกต้อง วิธีแก้ปัญหาที่เป็นประโยชน์มาก โดยเฉพาะอย่างยิ่งในกรณีของฉันที่ไม่ได้รับอนุญาตให้เปลี่ยนลายเซ็นของวิธีการที่มีหลายอาร์กิวเมนต์
AbhijeetMishra

นี่เป็นทางออกที่ดี มีวิธีรับค่าตอบแทนจากวิธีที่เรียกใช้ด้วยเทคนิคนี้หรือไม่?
David Pettigrew

15
คุณระบุความล่าช้าด้วยเทคนิคนี้ได้อย่างไร?
death_au

4
@death_au แทนที่จะinvokeโทร: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; ฉันต้องยอมรับว่านี่เป็นทางออกที่ยอดเยี่ยม ขอให้ทุกคนมีความสุขในการเขียนโค้ด!
Maxim Chetrusca

2
เป็นคนคุยสาย แต่มีคำถาม dropDownDelegate คืออะไร?
Minestrone-Soup

98

เพราะไม่มีสิ่งที่เรียกว่า[NSObject performSelector:withObject:withObject:afterDelay:]วิธีการ.

คุณต้องห่อหุ้มข้อมูลที่คุณต้องการส่งไปยัง Objective C object (เช่น NSArray, NSDictionary, Objective C type ที่กำหนดเอง) จากนั้นจึงส่งผ่าน[NSObject performSelector:withObject:afterDelay:]วิธีการที่เป็นที่รู้จักและชื่นชอบ

ตัวอย่างเช่น:

NSArray * arrayOfThingsIWantToPassAlong = 
    [NSArray arrayWithObjects: @"first", @"second", nil];

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];

ฉันไม่ได้รับข้อผิดพลาดหากฉันลบพารามิเตอร์ afterDelay หมายความว่า afterDelay ไม่ได้รับอนุญาตให้ใช้กับพารามิเตอร์มากกว่าหนึ่งตัวหรือไม่?
สุชี

1
คุณไม่ได้รับข้อผิดพลาด แต่ฉันพนันได้เลยว่าคุณจะได้รับข้อยกเว้น "ไม่พบตัวเลือก" ในขณะทำงาน (และสิ่งที่คุณพยายามดำเนินการจะไม่ถูกเรียก) ... ลองดูสิ :-)
Michael Dautermann

ฉันจะพิมพ์ Bool ที่นี่ได้อย่างไร
virata

ทำให้เป็นวัตถุสไตล์ Objective C (เช่น " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") และส่งต่อเป็นพารามิเตอร์เดียวคือ @virata
Michael Dautermann

2
นั่นเป็นคำถามแยกต่างหาก @Raj ... โปรดโพสต์แยกกัน
Michael Dautermann

34

คุณสามารถรวมพารามิเตอร์ของคุณไว้ในออบเจ็กต์เดียวและใช้วิธีการช่วยเหลือเพื่อเรียกเมธอดเดิมของคุณว่า Michael และตอนนี้คนอื่น ๆ ได้แนะนำ

อีกทางเลือกหนึ่งคือ dispatch_after ซึ่งจะใช้เวลาบล็อกและจัดคิวในช่วงเวลาหนึ่ง

double delayInSeconds = 15.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

    [self fooFirstInput:first secondInput:second];

});

หรือตามที่คุณได้ค้นพบแล้วหากคุณไม่ต้องการความล่าช้าคุณก็สามารถใช้ได้ - performSelector:withObject:withObject:


สิ่งที่ดีเกี่ยวกับวิธีนี้ก็คือคุณสามารถใช้__weakเพื่อให้ตัวจับเวลาแสร้งทำเป็นเพียงลิงค์ที่อ่อนแอกลับสู่ตัวเอง - ดังนั้นคุณจะไม่ต้องยืดอายุการใช้งานของวัตถุของคุณอย่างเทียมและเช่นถ้าคุณ performSelector: afterDelay: ส่งผลบางอย่างเช่น tail การเรียกซ้ำ (แม้ว่าจะไม่มีการเรียกซ้ำ) จากนั้นจะแก้ไขวงจรการเก็บรักษา
Tommy

1
ใช่นี่ควรเป็นคำตอบที่ยอมรับได้ เหมาะสมกว่าและตรงไปตรงมา
Roohul

7

ตัวเลือกที่ง่ายที่สุดคือการแก้ไขวิธีการของคุณเพื่อรับพารามิเตอร์เดียวที่มีทั้งสองอาร์กิวเมนต์เช่นNSArrayหรือNSDictionary(หรือเพิ่มวิธีที่สองที่ใช้พารามิเตอร์เดียวคลายแพ็กและเรียกใช้เมธอดแรกจากนั้นเรียกวิธีที่สองบน a ล่าช้า)

ตัวอย่างเช่นคุณอาจมีสิ่งต่างๆเช่น:

- (void) fooOneInput:(NSDictionary*) params {
    NSString* param1 = [params objectForKey:@"firstParam"];
    NSString* param2 = [params objectForKey:@"secondParam"];
    [self fooFirstInput:param1 secondInput:param2];
}

จากนั้นจะเรียกมันว่าคุณสามารถทำได้:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];

จะเกิดอะไรขึ้นถ้าไม่สามารถแก้ไขวิธีการได้ให้บอกว่าอยู่ใน UIKit หรืออะไร? ไม่เพียงเท่านั้นการเปลี่ยนวิธีมาใช้NSDictionaryความปลอดภัยแบบสูญเสียอีกด้วย ไม่เหมาะ
fatuhoku

@fatuhoku - ครอบคลุมโดยวงเล็บ; "เพิ่มวิธีที่สองที่ใช้พารามิเตอร์เดียวคลายแพ็กและเรียกใช้เมธอดแรก" วิธีนี้ใช้ได้ผลไม่ว่าวิธีแรกจะอยู่ที่ใด สำหรับความปลอดภัยประเภทนั้นหายไปทันทีที่ตัดสินใจใช้performSelector:(หรือNSInvocation) หากเป็นข้อกังวลตัวเลือกที่ดีที่สุดน่าจะเป็นการใช้ GCD
โร ธ

6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


- (void) fooFirstInput:(NSString*) first secondInput:(NSString*) second
{

}

และเรียกมันด้วย:

[self performSelector:@selector(callFooWithArray) withObject:[NSArray arrayWithObjects:@"first", @"second", nil] afterDelay:15.0];

5

คุณสามารถค้นหาวิธีการ performSelector: ทุกประเภทได้ที่นี่:

http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html

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

- performSelector:
- performSelector:withObject:
- performSelector:withObject:withObject:
– performSelector:withObject:afterDelay:
– performSelector:withObject:afterDelay:inModes:
– performSelectorOnMainThread:withObject:waitUntilDone:
– performSelectorOnMainThread:withObject:waitUntilDone:modes:
– performSelector:onThread:withObject:waitUntilDone:
– performSelector:onThread:withObject:waitUntilDone:modes:
– performSelectorInBackground:withObject: 

2

ฉันไม่ชอบวิธี NSInvocation ซับซ้อนเกินไป ทำให้เรียบง่ายและสะอาด:

// Assume we have these variables
id target, SEL aSelector, id parameter1, id parameter2;

// Get the method IMP, method is a function pointer here.
id (*method)(id, SEL, id, id) = (void *)[target methodForSelector:aSelector];

// IMP is just a C function, so we can call it directly.
id returnValue = method(target, aSelector, parameter1, parameter2);

ดี! แทนที่ 'vc' ด้วย 'target'
Anton

1

ฉันเพิ่งทำอะไรบางอย่างและจำเป็นต้องเรียกใช้วิธีการเดิม สิ่งที่ฉันทำคือการสร้างโปรโตคอลและส่งวัตถุของฉันไปที่มัน อีกวิธีหนึ่งคือกำหนดวิธีการในหมวดหมู่ แต่จะต้องมีการระงับคำเตือน (#pragma clang การวินิจฉัยละเว้น "-Wincomplete -plement")


0

วิธีง่ายๆและใช้ซ้ำได้คือการขยายNSObjectและนำไปใช้

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments;

สิ่งที่ต้องการ:

- (void)performSelector:(SEL)aSelector withObjects:(NSArray *)arguments
{
    NSMethodSignature *signature = [self methodSignatureForSelector: aSelector];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: signature];
    [invocation setSelector: aSelector];

    int index = 2; //0 and 1 reserved
    for (NSObject *argument in arguments) {
        [invocation setArgument: &argument atIndex: index];
        index ++;
    }
    [invocation invokeWithTarget: self];
}

0

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

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