iOS - Làm cách nào để triển khai một PerformSelector với nhiều đối số và với afterDelay?


90

Tôi là một người mới sử dụng iOS. Tôi có một phương pháp bộ chọn như sau:

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

}

Tôi đang cố gắng triển khai một cái gì đó như thế này -

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

Nhưng điều đó khiến tôi gặp lỗi khi nói -

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

Bất kỳ ý tưởng nào về những gì tôi đang thiếu?

Câu trả lời:


142

Cá nhân tôi nghĩ rằng một giải pháp gần gũi hơn với nhu cầu của bạn là sử dụng NSInvocation.

Một cái gì đó như sau sẽ thực hiện công việc:

indexPath dataSource là hai biến cá thể được định nghĩa trong cùng một phương pháp.

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
Đã đồng ý. Nó phải là câu trả lời chính xác. Giải pháp rất hữu ích. Đặc biệt trong trường hợp của tôi, nơi không được phép thay đổi chữ ký của phương thức chứa nhiều đối số.
AbhijeetMishra

Đây có vẻ như là một giải pháp tuyệt vời. Có cách nào để nhận giá trị trả về từ phương thức đang được gọi bằng kỹ thuật này không?
David Pettigrew

15
Làm thế nào để bạn chỉ định sự chậm trễ với kỹ thuật này?
death_au

4
@death_au, thay vì invokegọi: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; Tôi phải đồng ý rằng đây là một giải pháp tuyệt vời. Chúc mọi người viết mã vui vẻ!
Maxim Chetrusca

2
Hơi muộn để nói chuyện, nhưng có một câu hỏi. DropDownDelegate là gì?
Minestrone-Soup

98

Bởi vì không có thứ gọi là [NSObject performSelector:withObject:withObject:afterDelay:]phương pháp.

Bạn cần đóng gói dữ liệu bạn muốn gửi vào một đối tượng Objective C đơn lẻ nào đó (ví dụ: NSArray, NSDictionary, một số loại Objective C tùy chỉnh) và sau đó chuyển nó qua [NSObject performSelector:withObject:afterDelay:]phương thức được nhiều người biết đến và yêu thích.

Ví dụ:

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

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

Tôi không gặp lỗi nếu xóa tham số AfterDelay. Điều đó có nghĩa là afterDelay không được phép sử dụng với nhiều hơn một tham số?
Suchi

1
bạn không gặp lỗi, nhưng tôi cá là bạn sẽ nhận được ngoại lệ "không tìm thấy bộ chọn" tại thời điểm chạy (và thứ bạn đang cố thực hiện sẽ không được gọi) ... hãy thử xem. :-)
Michael Dautermann

Làm cách nào để vượt qua loại Bool ở đây?
virata

Biến nó thành một đối tượng kiểu Objective C (ví dụ: " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") và chuyển nó cùng với một tham số, @virata.
Michael Dautermann 11/10/12

2
đó là một câu hỏi riêng biệt @Raj ... xin vui lòng đăng nó riêng biệt.
Michael Dautermann 12/10/12

34

Bạn có thể đóng gói các tham số của mình vào một đối tượng và sử dụng phương thức trợ giúp để gọi phương thức ban đầu của bạn là Michael, và những người khác hiện đã đề xuất.

Một tùy chọn khác là send_ after, sẽ lấy một khối và xếp nó vào hàng đợi tại một thời điểm nhất định.

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

});

Hoặc, như bạn đã phát hiện ra, nếu không cần thời gian trễ, bạn có thể sử dụng - performSelector:withObject:withObject:


Điều tốt về cách tiếp cận này là bạn có thể sử dụng __weakđể cung cấp cho bộ đếm thời gian giả vờ của mình chỉ một liên kết yếu trở lại chính nó - vì vậy bạn không thể kéo dài vòng đời của đối tượng một cách giả tạo và ví dụ: nếu biểu diễn của bạn: afterDelay: hiệu ứng một cái gì đó giống như đuôi đệ quy (mặc dù không có đệ quy) sau đó nó giải quyết chu trình giữ lại.
Tommy

1
Có, đây phải là câu trả lời được chấp nhận. Nó thích hợp hơn và thẳng về phía trước.
Roohul

7

Tùy chọn đơn giản nhất là sửa đổi phương thức của bạn để nhận một tham số duy nhất chứa cả hai đối số, chẳng hạn như dấu NSArrayhoặc NSDictionary(hoặc thêm phương thức thứ hai nhận một tham số duy nhất, giải nén nó và gọi phương thức đầu tiên, sau đó gọi phương thức thứ hai trên sự chậm trễ).

Ví dụ, bạn có thể có một cái gì đó như:

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

Và sau đó để gọi nó, bạn có thể làm:

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

Điều gì sẽ xảy ra nếu phương thức không thể được sửa đổi, chẳng hạn như nó nằm trong UIKit hoặc thứ gì đó? Không chỉ vậy, việc thay đổi phương pháp sử dụng cũng NSDictionarylàm mất đi độ an toàn của loại. Không lý tưởng.
fatuhoku

@fatuhoku - Được bao phủ bởi dấu ngoặc đơn; "thêm một phương thức thứ hai nhận một tham số duy nhất, giải nén nó và gọi phương thức đầu tiên". Điều đó hoạt động bất kể phương thức đầu tiên sống ở đâu. Đối với an toàn kiểu, điều đó đã bị mất ngay khi quyết định sử dụng performSelector:(hoặc NSInvocation). Nếu đó là một mối quan tâm, lựa chọn tốt nhất có lẽ sẽ là chuyển qua GCD.
aroth

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


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

}

và gọi nó bằng:

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

5

Bạn có thể tìm thấy tất cả các loại phương thức performanceSelector: được cung cấp tại đây:

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

Có rất nhiều biến thể nhưng không có phiên bản nào có nhiều đối tượng cũng như độ trễ. Thay vào đó, bạn sẽ cần phải kết hợp các đối số của mình trong một NSArray hoặc 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

Tôi không thích cách NSInvocation, quá phức tạp. Hãy giữ cho nó đơn giản và sạch sẽ:

// 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);

Đẹp! Thay thế 'vc' bằng 'target'
Anton

1

Tôi vừa thực hiện một số thao tác lộn xộn và cần gọi phương thức ban đầu. Những gì tôi đã làm là tạo một giao thức và truyền đối tượng của tôi tới nó. Một cách khác là xác định phương pháp trong một danh mục, nhưng sẽ cần loại bỏ cảnh báo (#pragma clang chẩn đoán bị bỏ qua "-Wincomplete-implement").


0

Một cách đơn giản và có thể tái sử dụng là mở rộng NSObjectvà triển khai

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

cái gì đó như:

- (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

Tôi sẽ chỉ tạo một đối tượng tùy chỉnh chứa tất cả các tham số của tôi dưới dạng thuộc tính và sau đó sử dụng đối tượng duy nhất đó làm tham số

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.