Cách sử dụng PerformSelector: withObject: afterDelay: với các loại nguyên thủy trong Cacao?


97

Các NSObjectphương pháp performSelector:withObject:afterDelay:cho phép tôi để gọi một phương pháp trên đối tượng với một đối số đối tượng sau một thời gian nhất định. Nó không thể được sử dụng cho các phương thức có đối số không phải đối tượng (ví dụ: ints, float, struct, con trỏ không phải đối tượng, v.v.).

Là gì đơn giản nhất cách để đạt được điều tương tự với một phương thức với một lập luận phi vật thể? Tôi biết rằng đối với thường xuyên performSelector:withObject:, giải pháp là sử dụng NSInvocation(nhân tiện thực sự phức tạp). Nhưng tôi không biết làm thế nào để xử lý phần "delay".

Cảm ơn,


Nó hơi khó hiểu nhưng tôi thấy nó hữu ích khi viết mã nhanh. Một cái gì đó như: id object = [array performanceSelector: @selector (objectAtIndex :) withObject: (__bridge_transfer) (void *) 1]; . Nếu đối số là 0, bạn thậm chí không cần cast bridge vì 0 là "đặc biệt" và id tương thích với nó.
Ramy Al Zuhouri

Câu trả lời:


72

Đây là những gì tôi thường gọi là thứ mà tôi không thể thay đổi bằng 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];

Tại sao không chỉ làm [theMovie setOrientation: UIInterfaceOrientationPortrait animated:NO]? Hay bạn có nghĩa là invokethông báo nằm trong phương pháp bạn thực hiện-sau khi trì hoãn?
Peter Hosey

À, tôi quên chưa nói .. `[anInvocation performanceSelector: @selector (gọi) afterDelay: 0.5];
Morty

24
chỉ là một lưu ý phụ cho những người không biết, chúng tôi bắt đầu thêm các đối số từ chỉ mục 2 vì chỉ mục 0 và 1 được dành riêng cho các đối số ẩn "self" và "_cmd".
Vishal Singh

35

Chỉ cần bọc float, boolean, int hoặc tương tự trong một NSNumber.

Đối với cấu trúc, tôi không biết có giải pháp hữu ích nào, nhưng bạn có thể tạo một lớp objC riêng sở hữu cấu trúc như vậy.


5
Tạo một phương thức trình bao bọc, -delayedMethodWithArgument: (NSNumber *) arg. Yêu cầu nó gọi phương thức gốc sau khi kéo phương thức nguyên thủy ra khỏi NSNumber.
James Williams

1
Hiểu. Sau đó, tôi không chắc chắn về một sollution thanh lịch, nhưng bạn có thể thêm một phương thức khác được dự định làm mục tiêu cho PerformSelector :, giải nén NSNumber và thực hiện bộ chọn cuối cùng theo phương pháp bạn hiện có trong đầu. Một ý tưởng khác mà bạn có thể khám phá là tạo một danh mục trên NSObject bổ sung thêm perforSelector: withInt: ... (và tương tự).
hại

32
NSValue (lớp cha của NSNumber) sẽ bọc bất cứ thứ gì bạn cung cấp cho nó. Nếu bạn muốn bao bọc một phiên bản của 'struct foo' được gọi là 'bar', bạn phải thực hiện '[NSValue valueWithBytes: & bar objCType: @encode (struct foo)];'
Jim Dovey

2
Bạn có thể sử dụng NSValue để bọc một cấu trúc. Dù bằng cách nào, NSNumber / NSValue là giải pháp chính xác.
Peter Hosey

6
Khẳng định: PHẢI vượt qua nilcho một BOOLtham số để nhận NO( FALSE)
Nicolas Miari

13

KHÔNG SỬ DỤNG CÂU TRẢ LỜI NÀY. TÔI CHỈ CÓ TRÁI CHO CÁC MỤC ĐÍCH LỊCH SỬ. XEM Ý KIẾN BÊN DƯỚI.

Có một thủ thuật đơn giản nếu đó là tham số BOOL.

Vượt qua con số không cho KHÔNG và bản thân là CÓ. nil được chuyển thành giá trị BOOL là NO. tự được chuyển thành giá trị BOOL là CÓ.

Cách tiếp cận này bị phá vỡ nếu nó là bất kỳ thứ gì khác ngoài tham số BOOL.

Giả sử bản thân là một 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];

Tôi tin rằng các giá trị BOOL phải luôn là 0 hoặc 1. Đúng là hầu hết các mã sẽ không quan tâm và chỉ đơn giản là if ()kiểm tra nó, nhưng có thể tưởng tượng rằng một số mã sẽ phụ thuộc vào nó là 0 hoặc 1, và do đó đã thắng 'không coi một số giá trị địa chỉ lớn giống như 1 (CÓ).
user102008

1
Cảm ơn vì điều đó, tôi không muốn tạo một trình bao bọc hoặc viết cú pháp GCD xấu xí để làm điều đó.
0xSina

10
KHÔNG DÙNG BẤT CỨ AI . Cách làm này là nguồn gốc của các lỗi nghiêm trọng, khó tìm. Hãy tưởng tượng selfvới địa chỉ như thế nào 0x0123400. Với cách tiếp cận này, Bạn sẽ KHÔNG nhận được chữ CÓ trong 0,4% trường hợp. Tệ hơn nữa, với xác suất này, giải pháp này có thể vượt qua tất cả các bài kiểm tra và tiết lộ sau đó.
Oleg Trakhman, 12/12/12

1
CẬP NHẬT: Người ta sẽ KHÔNG nhận được chữ CÓ với xác suất 6% vì căn chỉnh bộ nhớ.
Oleg Trakhman, 12/12/12

4
@iVishal Kiểm tra góc nhọn BOOL của
Farray

8

Có lẽ NSValue , chỉ cần đảm bảo các con trỏ của bạn vẫn hợp lệ sau thời gian trì hoãn (tức là không có đối tượng nào được phân bổ trên ngăn xếp).


Có bất kỳ phương pháp cũ nào "mở hộp" một NSValue(nếu có thể) với mong đợi typekhông?
Alex Gray

8

Tôi biết đây là một câu hỏi cũ nhưng nếu bạn đang xây dựng iOS SDK 4+ thì bạn có thể sử dụng các khối để thực hiện việc này với rất ít nỗ lực và làm cho nó dễ đọc hơn:

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];     
});

Điều này tạo ra một vòng lặp giữ lại. Để điều này hoạt động bình thường, bạn cần phải tạo một tham chiếu yếu cho bản thân và sử dụng tham chiếu đó để thực hiện bộ chọn. "__block typeof (self) thinSelf = bản thân;"
Tim Wachter 14/1113

1
Bạn không cần tham chiếu yếu ở đây vì self không giữ lại khối (không có vòng lặp giữ lại). Có lẽ tốt hơn là sử dụng một tham chiếu mạnh thực sự vì bản thân có thể bị phân bổ theo cách khác. Xem: stackoverflow.com/a/19018633/251687
Sebastien Martin

6

PerformSelector: WithObject luôn nhận một đối tượng, vì vậy để truyền các đối số như int / double / float, v.v. Bạn có thể sử dụng một cái gì đó như thế này.

//NSNumber is an object..

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

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

     [txtUsername setAlpha: [number floatValue] ];
    }

Theo cách tương tự, bạn có thể sử dụng [NSNumber numberWithInt:] v.v .... và trong phương thức nhận, bạn có thể chuyển đổi số thành định dạng của mình là [số int] hoặc [số kép].


1
Nếu một người bị hạn chế đối với + performanceSelector: withObject: +, thì đây là câu trả lời đúng duy nhất được đăng, IMO. Nhưng câu trả lời của @ MichaelGaylord bằng cách sử dụng khối sẽ rõ ràng hơn, nếu đó là một lựa chọn cho bạn.
big_m

5

Các khối là con đường để đi. Bạn có thể có các tham số phức tạp, loại an toàn, và nó đơn giản và an toàn hơn rất nhiều so với hầu hết các câu trả lời cũ ở đây. Ví dụ, bạn chỉ có thể viết:

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

Các khối cho phép bạn nắm bắt danh sách tham số, đối tượng tham chiếu và biến tùy ý.

Thực hiện sao lưu (cơ bản):

@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

Thí dụ:

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;
}

Cũng xem câu trả lời của Michael (+1) cho một ví dụ khác.


3

Tôi luôn khuyên bạn nên sử dụng NSMutableArray làm đối tượng để chuyển. Điều này là do sau đó bạn có thể chuyển một số đối tượng, như nút được nhấn và các giá trị khác. NSNumber, NSInteger và NSString chỉ là các vùng chứa một số giá trị. Đảm bảo rằng khi bạn lấy đối tượng từ mảng mà bạn tham chiếu đến một loại vùng chứa chính xác. Bạn cần chuyển các vùng chứa NS. Ở đó bạn có thể kiểm tra giá trị. Hãy nhớ rằng vùng chứa sử dụng isEqual khi các giá trị được so sánh.

#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

Tôi cũng muốn làm điều này, nhưng với một phương thức nhận tham số BOOL. Bao bọc giá trị bool bằng NSNumber, KHÔNG THỂ CHUYỂN ĐỔI GIÁ TRỊ. Tôi không biết tại sao.

Vì vậy, tôi đã thực hiện một vụ hack đơn giản. Tôi đặt tham số bắt buộc trong một hàm giả khác và gọi hàm đó bằng hàm PerformSelector, trong đó withObject = nil;

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

-(void)dummyCaller {

[self myFunction:YES];

}

Xem bình luận của tôi ở trên về câu trả lời của các tác hại. Có vẻ như, vì -performSelector[...]phương thức mong đợi một đối tượng (thay vì một kiểu dữ liệu nguyên thủy) và nó không biết rằng bộ chọn được gọi chấp nhận một boolean, có lẽ địa chỉ (giá trị con trỏ) của NSNumbercá thể bị 'ép kiểu' thành BOOL(= khác không, tức là TRUE). Có lẽ thời gian chạy có thể thông minh hơn thế này và nhận ra một boolean bằng không được bao bọc trong dấu NSNumber!
Nicolas Miari

Tôi đồng ý. Tôi đoán một điều tốt hơn nên làm là tránh hoàn toàn BOOL và sử dụng số nguyên 0 cho KHÔNG và 1 cho CÓ, và bọc điều đó bằng NSNumber hoặc NSValue.
GeneCode

Điều đó có thay đổi gì không? Nếu vậy, đột nhiên tôi thực sự thất vọng với Cocoa :)
Nicolas Miari

2

Tôi thấy rằng cách nhanh nhất (nhưng hơi bẩn) để làm điều này là gọi trực tiếp objc_msgSend. Tuy nhiên, thật nguy hiểm nếu gọi nó trực tiếp vì bạn cần đọc tài liệu và đảm bảo rằng bạn đang sử dụng đúng biến thể cho loại giá trị trả về và vì objc_msgSend được định nghĩa là vararg để thuận tiện cho trình biên dịch nhưng thực sự được triển khai dưới dạng keo lắp ráp nhanh . Đây là một số mã được sử dụng để gọi một phương thức ủy quyền - [Delegate integerDidChange:] nhận một đối số số nguyên.

#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);
}

Điều này đầu tiên sẽ lưu bộ chọn vì chúng ta sẽ tham khảo nó nhiều lần và sẽ dễ dàng tạo lỗi đánh máy. Sau đó, nó xác minh rằng người đại diện thực sự phản hồi với bộ chọn - nó có thể là một giao thức tùy chọn. Sau đó, nó tạo ra một kiểu con trỏ hàm chỉ định chữ ký thực của bộ chọn. Hãy nhớ rằng tất cả các thông báo Objective-C đều có hai đối số ẩn đầu tiên, đối tượng đang được thông báo và bộ chọn đang được gửi đi. Sau đó, chúng tôi tạo một con trỏ hàm có kiểu thích hợp và đặt nó trỏ đến hàm objc_msgSend bên dưới. Hãy nhớ rằng nếu giá trị trả về là float hoặc struct, bạn cần sử dụng một biến thể khác của objc_msgSend. Cuối cùng, gửi tin nhắn bằng cùng một máy móc mà Objective-C sử dụng dưới các trang tính.


"Hãy ghi nhớ rằng nếu bạn gửi phao hoặc cấu trúc ..." bạn có nghĩa là trở về phao hoặc struct
user102008

Ba dòng đó cung cấp cho bạn sự an toàn về kiểu chữ và đảm bảo rằng số lượng đối số là những gì bộ chọn của bạn mong đợi. objc_msgSend là một hàm vararg thực sự được triển khai dưới dạng keo lắp ráp, vì vậy nếu bạn không đánh máy, bạn có thể cho nó bất cứ thứ gì.
Jason Harris

1

Bạn chỉ có thể sử dụng NSTimer để gọi một bộ chọn:

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

Bạn đang thiếu đối số. Đối số yourMethod:trong ví dụ của bạn là chính bộ đếm thời gian và bạn chưa vượt qua bất kỳ điều gì cho userInfo. Ngay cả khi bạn đã sử dụng userInfo, bạn vẫn sẽ phải đóng hộp giá trị, như những tác hại và Remus Rusanu đề nghị.
Peter Hosey

-1 vì không sử dụng PerformSelector và tạo phiên bản NSTimer không cần thiết
Nhà phát triển iOS của Applicationasa

1

Gọi performanceSelector với NSNumber hoặc NSValue khác sẽ không hoạt động. Thay vì sử dụng giá trị của NSValue / NSNumber, nó sẽ truyền con trỏ đến int, float hoặc bất cứ thứ gì một cách hiệu quả và sử dụng nó.

Nhưng giải pháp rất đơn giản và rõ ràng. Tạo NSInvocation và gọi

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


0

Có lẽ ... được rồi, rất có thể, tôi đang thiếu thứ gì đó, nhưng tại sao không chỉ tạo một kiểu đối tượng, chẳng hạn như NSNumber, làm vùng chứa cho biến kiểu không phải đối tượng của bạn, chẳng hạn như CGFloat?

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

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

Câu hỏi là về việc truyền các kiểu nguyên thủy, không phải đối tượng NSNumber.
Rudolf Adamkovič

Câu hỏi giả sử rằng OP chỉ có quyền truy cập bên ngoài vào phương thức và không thể thay đổi chữ ký.
Alex Gray
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.