วิธีใช้ performSelector: withObject: afterDelay: กับประเภทดั้งเดิมในโกโก้?


97

NSObjectวิธีการperformSelector:withObject:afterDelay:ช่วยให้ผมเรียกวิธีการเกี่ยวกับวัตถุที่มีการโต้เถียงวัตถุหลังจากระยะเวลาหนึ่ง ไม่สามารถใช้สำหรับเมธอดที่มีอาร์กิวเมนต์ที่ไม่ใช่อ็อบเจ็กต์ (เช่น ints, floats, structs, non-object pointers เป็นต้น)

เป็นอะไรที่ง่ายวิธีการเพื่อให้บรรลุสิ่งเดียวกันกับวิธีการที่มีข้อโต้แย้งที่ไม่ใช่วัตถุ? ฉันรู้ว่าสำหรับปกติperformSelector:withObject:วิธีแก้ปัญหาคือการใช้NSInvocation(ซึ่งมันซับซ้อนมาก) แต่ฉันไม่รู้ว่าจะจัดการกับส่วน "หน่วงเวลา" อย่างไร

ขอบคุณ


มันค่อนข้างแฮ็ค แต่ฉันคิดว่าการเขียนโค้ดด่วนมีประโยชน์ สิ่งที่ต้องการ: id object = [array performSelector: @selector (objectAtIndex :) withObject: (__bridge_transfer) (void *) 1]; . หากอาร์กิวเมนต์เป็น 0 คุณไม่จำเป็นต้องใช้บริดจ์คาสต์ด้วยซ้ำเพราะ 0 เป็น "พิเศษ" และ id เข้ากันได้กับมัน
Ramy Al Zuhouri

คำตอบ:


72

นี่คือสิ่งที่ฉันใช้เรียกสิ่งที่ฉันไม่สามารถเปลี่ยนแปลงได้โดยใช้ NSInvocation:

SEL theSelector = NSSelectorFromString(@"setOrientation:animated:");
NSInvocation *anInvocation = [NSInvocation
            invocationWithMethodSignature:
            [MPMoviePlayerController instanceMethodSignatureForSelector:theSelector]];

[anInvocation setSelector:theSelector];
[anInvocation setTarget:theMovie];
UIInterfaceOrientation val = UIInterfaceOrientationPortrait;
BOOL anim = NO;
[anInvocation setArgument:&val atIndex:2];
[anInvocation setArgument:&anim atIndex:3];

[anInvocation performSelector:@selector(invoke) withObject:nil afterDelay:1];

ทำไมไม่ทำ[theMovie setOrientation: UIInterfaceOrientationPortrait animated:NO]? หรือคุณหมายความว่าinvokeข้อความอยู่ในวิธีการที่คุณดำเนินการหลังจากล่าช้า?
Peter Hosey

อ้อลืมบอกไป .. `[anInvocation performSelector: @selector (invoke) afterDelay: 0.5];
Morty

25
เพียงแค่หมายเหตุด้านข้างสำหรับผู้ที่ไม่รู้เราเริ่มเพิ่มอาร์กิวเมนต์จากดัชนี 2 เนื่องจากดัชนี 0 และ 1 สงวนไว้สำหรับอาร์กิวเมนต์ที่ซ่อนอยู่ "self" และ "_cmd"
Vishal Singh

35

เพียงห่อโฟลตบูลีน int หรือคล้ายกันใน NSNumber

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


5
สร้างเมธอด wrapper -delayedMethodWithArgument: (NSNumber *) arg ให้เรียกวิธีการดั้งเดิมหลังจากดึงดั้งเดิมออกจาก NSNumber
James Williams

1
เข้าใจแล้ว. จากนั้นฉันไม่แน่ใจว่ามีการละลายที่สวยงาม แต่คุณสามารถเพิ่มวิธีการอื่นที่มีไว้สำหรับเป้าหมายของ performSelector ของคุณ: ซึ่งจะแกะ NSNumber และดำเนินการเลือกขั้นสุดท้ายตามวิธีการที่คุณคิดอยู่ แนวคิดอีกประการหนึ่งที่คุณสามารถสำรวจได้คือการสร้างหมวดหมู่ใน NSObject ซึ่งเพิ่ม perforSelector: withInt: ...
อันตราย

32
NSValue (ซูเปอร์คลาสของ NSNumber) จะห่อทุกสิ่งที่คุณมอบให้ หากคุณต้องการรวมอินสแตนซ์ของ 'struct foo' ที่เรียกว่า 'bar' คุณจะต้องทำ '[NSValue valueWithBytes: & bar objCType: @encode (struct foo)];'
Jim Dovey

2
คุณสามารถใช้ NSValue เพื่อรวมโครงสร้าง ไม่ว่าจะด้วยวิธีใด NSNumber / NSValue เป็นวิธีแก้ปัญหาที่ถูกต้อง
Peter Hosey

6
ยืนยัน: ต้องผ่านการnilหาBOOLพารามิเตอร์ที่จะได้รับNO( FALSE)
นิโคลัส Miari

13

อย่าใช้คำตอบนี้ ฉันเหลือไว้เพียงเพื่อวัตถุประสงค์ทางประวัติศาสตร์เท่านั้น ดูความคิดเห็นด้านล่าง

มีเคล็ดลับง่ายๆหากเป็นพารามิเตอร์ BOOL

ผ่านศูนย์สำหรับไม่ใช่และตัวเองเพื่อใช่ ศูนย์ถูกส่งไปยังค่า BOOL ของ NO ตัวเองถูกเหวี่ยงไปที่ค่า BOOL ของ YES

วิธีนี้แยกย่อยหากเป็นสิ่งอื่นที่ไม่ใช่พารามิเตอร์ BOOL

สมมติว่าตัวเองเป็น UIView

//nil will be cast to NO when the selector is performed
[self performSelector:@selector(setHidden:) withObject:nil afterDelay:5.0];

//self will be cast to YES when the selector is performed
[self performSelector:@selector(setHidden:) withObject:self afterDelay:10.0];

ฉันเชื่อว่าค่า BOOL ควรเป็น 0 หรือ 1 เสมอมันเป็นความจริงที่โค้ดส่วนใหญ่จะไม่สนใจและจะทำการif ()ทดสอบ แต่ก็เป็นไปได้ว่าโค้ดบางตัวจะขึ้นอยู่กับว่ามันเป็น 0 หรือ 1 จึงชนะ ไม่ถือว่าค่าที่อยู่ขนาดใหญ่บางค่าเหมือนกับ 1 (ใช่)
user102008

1
ขอบคุณที่ไม่ต้องการสร้าง Wrapper หรือเขียนไวยากรณ์ GCD ที่น่าเกลียดเพื่อทำเช่นนั้น
0xSina

10
ทุกคนไม่ได้ใช้มัน แนวทางนี้เป็นที่มาของข้อบกพร่องร้ายแรงหายาก ลองนึกภาพที่มีอยู่เช่นself 0x0123400ด้วยวิธีนี้คุณจะได้รับNO insted of YES ใน 0.4%ของกรณี ยิ่งไปกว่านั้นด้วยความน่าจะเป็นนี้วิธีนี้อาจผ่านการทดสอบทั้งหมดและเปิดเผยในภายหลัง
Oleg Trakhman

1
UPD: หนึ่งจะได้รับ NO insted of YES ด้วยความน่าจะเป็น 6% เนื่องจากการจัดตำแหน่งหน่วยความจำ
Oleg Trakhman


8

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


วิธีการเก่า ๆ จะ "unbox" an NSValue(ถ้าเป็นไปได้) ให้เป็นไปตามที่คาดไว้typeหรือไม่?
Alex Gray

8

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ถ้าคุณกำลังสร้าง iOS SDK 4+ คุณสามารถใช้บล็อกเพื่อทำสิ่งนี้ได้โดยใช้ความพยายามเพียงเล็กน้อยและทำให้อ่านง่ายขึ้น:

double delayInSeconds = 2.0;
int primitiveValue = 500;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self doSomethingWithPrimitive:primitiveValue];     
});

สิ่งนี้จะสร้างห่วงการเก็บรักษา เพื่อให้สิ่งนี้ทำงานได้อย่างถูกต้องคุณต้องทำการอ้างอิงที่อ่อนแอถึงตัวเองและใช้การอ้างอิงนั้นเพื่อดำเนินการเลือก "__block typeof (ตัวเอง) อ่อนแอตัวเอง = ตัวเอง;"
Tim Wachter

1
คุณไม่จำเป็นต้องมีการอ้างอิงที่อ่อนแอที่นี่เพราะตัวเองไม่ได้รักษาบล็อกไว้ (ไม่มีการวนซ้ำการเก็บรักษา) อาจดีกว่าที่จะใช้การอ้างอิงที่ชัดเจนเนื่องจากตัวเองอาจถูกยกเลิกการจัดสรรเป็นอย่างอื่น ดู: stackoverflow.com/a/19018633/251687
Sebastien Martin

6

PerformSelector: WithObject ใช้วัตถุเสมอดังนั้นในการส่งผ่านอาร์กิวเมนต์เช่น int / double / float เป็นต้น ..... คุณสามารถใช้สิ่งนี้ได้

//NSNumber is an object..

    [self performSelector:@selector(setUserAlphaNumber:) withObject: [NSNumber numberWithFloat: 1.0f]
    afterDelay:1.5];

    -(void) setUserAlphaNumber: (NSNumber*) number{

     [txtUsername setAlpha: [number floatValue] ];
    }

วิธีเดียวกับที่คุณสามารถใช้ [NSNumber numberWithInt:] ฯลฯ .... และในวิธีการรับคุณสามารถแปลงตัวเลขเป็นรูปแบบของคุณเป็น [number int] หรือ [number double]


1
หากมีการ จำกัด ไว้ที่ + performSelector: withObject: + แสดงว่านี่เป็นคำตอบเดียวที่ถูกโพสต์ IMO แต่คำตอบของ @MichaelGaylord โดยใช้บล็อกนั้นสะอาดกว่าหากเป็นตัวเลือกสำหรับคุณ
big_m

5

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

[MONBlock performBlock:^{[obj setFrame:SOMETHING];} afterDelay:2];

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

Backing Implementation (พื้นฐาน):

@interface MONBlock : NSObject

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay;

@end

@implementation MONBlock

+ (void)imp_performBlock:(void(^)())pBlock
{
 pBlock();
}

+ (void)performBlock:(void(^)())pBlock afterDelay:(NSTimeInterval)pDelay
{
  [self performSelector:@selector(imp_performBlock:)
             withObject:[pBlock copy]
             afterDelay:pDelay];
}

@end

ตัวอย่าง:

int main(int argc, const char * argv[])
{
 @autoreleasepool {
  __block bool didPrint = false;
  int pi = 3; // close enough =p

  [MONBlock performBlock:^{NSLog(@"Hello, World! pi is %i", pi); didPrint = true;} afterDelay:2];

  while (!didPrint) {
   [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeInterval:0.1 sinceDate:NSDate.date]];
  }

  NSLog(@"(Bye, World!)");
 }
 return 0;
}

ดูคำตอบของ Michael (+1) ด้วยเช่นกัน


3

ฉันขอแนะนำเสมอว่าคุณใช้ NSMutableArray เป็นวัตถุที่จะส่งต่อ เนื่องจากคุณสามารถส่งผ่านวัตถุต่างๆเช่นปุ่มที่กดและค่าอื่น ๆ NSNumber, NSInteger และ NSString เป็นเพียงคอนเทนเนอร์ที่มีค่าบางอย่าง ตรวจสอบให้แน่ใจว่าเมื่อคุณได้รับวัตถุจากอาร์เรย์ที่คุณอ้างถึงประเภทคอนเทนเนอร์ที่ถูกต้อง คุณต้องส่งต่อคอนเทนเนอร์ NS คุณสามารถทดสอบค่าได้ที่นั่น โปรดจำไว้ว่าคอนเทนเนอร์ใช้ isEqual เมื่อเปรียบเทียบค่า

#define DELAY_TIME 5

-(void)changePlayerGameOnes:(UIButton*)sender{
    NSNumber *nextPlayer = [NSNumber numberWithInt:[gdata.currentPlayer intValue]+1 ];
    NSMutableArray *array = [[NSMutableArray alloc]initWithObjects:sender, nil];
    [array addObject:nextPlayer];
    [self performSelector:@selector(next:) withObject:array afterDelay:DELAY_TIME];
}
-(void)next:(NSMutableArray*)nextPlayer{
    if(gdata != nil){ //if game choose next player
       [self nextPlayer:[nextPlayer objectAtIndex:1] button:[nextPlayer objectAtIndex:0]];
    }
}

2

ฉันต้องการทำเช่นนี้ด้วย แต่ด้วยวิธีการที่ได้รับพารามิเตอร์ BOOL การตัดค่าบูลด้วย NSNumber, FAILED TO PASS THE VALUE ฉันมีความคิดว่าทำไมไม่มี.

ดังนั้นฉันจึงทำการแฮ็คง่ายๆ ฉันใส่พารามิเตอร์ที่ต้องการในฟังก์ชันดัมมี่อื่นและเรียกใช้ฟังก์ชันนั้นโดยใช้ performSelector โดยที่ withObject = nil;

[self performSelector:@selector(dummyCaller:) withObject:nil afterDelay:5.0];

-(void)dummyCaller {

[self myFunction:YES];

}

ดูความคิดเห็นของฉันด้านบนเกี่ยวกับคำตอบของอันตราย ดูเหมือนว่าเนื่องจาก-performSelector[...]เมธอดคาดหวังออบเจ็กต์ (แทนที่จะเป็นชนิดข้อมูลดั้งเดิม) และไม่ทราบว่าตัวเลือกที่เรียกนั้นยอมรับบูลีนบางทีที่อยู่ (ค่าตัวชี้) ของNSNumberอินสแตนซ์จะสุ่มสี่สุ่มห้า 'ส่ง' ถึงBOOL(= ไม่ใช่ศูนย์กล่าวคือTRUE) บางทีรันไทม์อาจฉลาดกว่านี้และรู้จักบูลีนเป็นศูนย์ที่ห่อด้วยNSNumber!
Nicolas Miari

ฉันเห็นด้วย. ฉันเดาว่าสิ่งที่ดีกว่าที่ต้องทำคือหลีกเลี่ยง BOOL โดยสิ้นเชิงและใช้จำนวนเต็ม 0 สำหรับ NO และ 1 สำหรับ YES และห่อด้วย NSNumber หรือ NSValue
GeneCode

มันเปลี่ยนแปลงอะไรไหม? ถ้าเป็นเช่นนั้นฉันก็ผิดหวังกับโกโก้อย่างแท้จริง :)
Nicolas Miari

2

ฉันพบว่าวิธีที่เร็วที่สุด (แต่ค่อนข้างสกปรก) ในการทำสิ่งนี้คือการเรียกใช้ objc_msgSend โดยตรง อย่างไรก็ตามการเรียกใช้โดยตรงเป็นเรื่องอันตรายเนื่องจากคุณต้องอ่านเอกสารและตรวจสอบให้แน่ใจว่าคุณใช้ตัวแปรที่ถูกต้องสำหรับประเภทของค่าส่งคืนและเนื่องจาก objc_msgSend ถูกกำหนดให้เป็น vararg เพื่อความสะดวกในการคอมไพเลอร์ แต่ใช้จริงเป็นกาวประกอบที่รวดเร็ว . นี่คือโค้ดบางส่วนที่ใช้เพื่อเรียกเมธอดผู้ร่วมประชุม - [delegate integerDidChange:] ที่ใช้อาร์กิวเมนต์จำนวนเต็มเดียว

#import <objc/message.h>


SEL theSelector = @selector(integerDidChange:);
if ([self.delegate respondsToSelector:theSelector])
{
    typedef void (*IntegerDidChangeFuncPtrType)(id, SEL, NSInteger);
    IntegerDidChangeFuncPtrType MyFunction = (IntegerDidChangeFuncPtrType)objc_msgSend;
    MyFunction(self.delegate, theSelector, theIntegerThatChanged);
}

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


"โปรดทราบว่าหากคุณกำลังส่งลอยหรือโครงสร้าง ... " คุณหมายถึงการส่งคืนลอยหรือโครงสร้าง
user102008

ทั้งสามบรรทัดให้ความปลอดภัยในการพิมพ์และรับประกันว่าจำนวนอาร์กิวเมนต์เป็นสิ่งที่ผู้เลือกของคุณคาดหวัง objc_msgSend เป็นฟังก์ชั่น vararg ที่ใช้เป็นกาวประกอบดังนั้นหากคุณไม่พิมพ์แคสต์คุณสามารถให้อะไรก็ได้
Jason Harris

1

คุณสามารถใช้ NSTimer เพื่อเรียกตัวเลือก:

[NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(yourMethod:) userInfo:nil repeats:NO]

คุณไม่มีอาร์กิวเมนต์ ข้อโต้แย้งyourMethod:ในตัวอย่างของคุณคือตัวจับเวลาเองและคุณไม่ได้ผ่านอะไรuserInfoเลย แม้ว่าคุณจะใช้งานuserInfoคุณก็ยังคงต้องเพิ่มมูลค่าตามที่เป็นอันตรายและ Remus Rusanu แนะนำ
Peter Hosey

-1 สำหรับการไม่ใช้ performSelector และสร้างอินสแตนซ์ NSTimer ที่ไม่จำเป็น
พัฒนา Applicasa iOS

1

การเรียกใช้ performSelector ด้วย NSNumber หรือ NSValue อื่น ๆ จะไม่ทำงาน แทนที่จะใช้ค่าของ NSValue / NSNumber มันจะส่งตัวชี้ได้อย่างมีประสิทธิภาพไปที่ int, float หรืออะไรก็ได้และใช้สิ่งนั้น

แต่วิธีแก้ปัญหานั้นง่ายและชัดเจน สร้าง NSInvocation และโทร

[invocation performSelector:@selector(invoke) withObject:nil afterDelay:delay]


0

โอเคเป็นไปได้มากว่าฉันขาดอะไรไป แต่ทำไมไม่สร้างประเภทอ็อบเจ็กต์พูด NSNumber เป็นคอนเทนเนอร์สำหรับตัวแปรประเภทที่ไม่ใช่อ็อบเจ็กต์ของคุณเช่น CGFloat

CGFloat myFloat = 2.0; 
NSNumber *myNumber = [NSNumber numberWithFloat:myFloat];

[self performSelector:@selector(MyCalculatorMethod:) withObject:myNumber afterDelay:5.0];

คำถามเกี่ยวกับการส่งผ่านประเภทดั้งเดิมไม่ใช่อ็อบเจ็กต์ NSNumber
Rudolf Adamkovič

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