iOS - Bagaimana cara mengimplementasikan performSelector dengan banyak argumen dan dengan afterDelay?

90

Saya seorang pemula iOS. Saya memiliki metode pemilih sebagai berikut -

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

}

Saya mencoba menerapkan sesuatu seperti ini -

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

Tapi itu memberi saya kesalahan mengatakan -

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

Ada gagasan tentang apa yang saya lewatkan?

Suchi
sumber

Jawaban:

143

Secara pribadi, saya pikir solusi yang lebih dekat dengan kebutuhan Anda adalah penggunaan NSInvocation.

Sesuatu seperti berikut ini akan bekerja:

indexPath dan dataSource adalah dua variabel instance yang ditentukan dalam metode yang sama.

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];
}
valvoline
sumber
2
Sepakat. Itu harus menjadi jawaban yang benar. Solusi yang sangat membantu. Khususnya dalam kasus saya di mana tidak diperbolehkan untuk mengubah tanda tangan dari metode yang mengandung banyak argumen.
AbhijeetMishra
Sepertinya ini solusi yang bagus. Apakah ada cara untuk mendapatkan nilai hasil dari metode yang dipanggil dengan teknik ini?
David Pettigrew
15
Bagaimana Anda menentukan penundaan dengan teknik ini?
death_au
4
@death_au, alih-alih invoke, hubungi: [inv performSelector:@selector(invoke) withObject:nil afterDelay:1]; Saya harus setuju ini adalah solusi yang bagus. Selamat membuat kode semuanya!
Maxim Chetrusca
2
Agak terlambat untuk percakapan, tapi punya pertanyaan. Apa itu dropDownDelegate?
Minestrone-Soup
97

Karena tidak ada yang namanya [NSObject performSelector:withObject:withObject:afterDelay:]metode.

Anda perlu merangkum data yang ingin Anda kirim ke dalam beberapa objek Objective C (misalnya NSArray, NSDictionary, beberapa jenis Objective C kustom) dan kemudian meneruskannya melalui [NSObject performSelector:withObject:afterDelay:]metode yang terkenal dan disukai.

Sebagai contoh:

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

[self performSelector:@selector(fooFirstInput:) 
           withObject:arrayOfThingsIWantToPassAlong  
           afterDelay:15.0];
Michael Dautermann
sumber
Saya tidak mendapatkan kesalahan jika saya menghapus parameter afterDelay. Apakah itu berarti afterDelay tidak diizinkan untuk digunakan dengan lebih dari satu parameter?
Suchi
1
Anda tidak mendapatkan kesalahan, tetapi saya berani bertaruh Anda akan mendapatkan pengecualian "selektor tidak ditemukan" pada waktu proses (dan hal yang Anda coba lakukan tidak akan dipanggil) ... coba dan lihat. :-)
Michael Dautermann
Bagaimana cara lulus tipe Bool di sini?
virata
Jadikan sebagai objek gaya Objective C (mis. " NSNumber * whatToDoNumber = [NSNumber numberWithBool: doThis];") Dan berikan sebagai parameter satu, @virata.
Michael Dautermann
2
itu pertanyaan terpisah @Raj ... tolong posting secara terpisah.
Michael Dautermann
34

Anda dapat mengemas parameter Anda menjadi satu objek dan menggunakan metode helper untuk memanggil metode asli Anda seperti yang disarankan Michael, dan yang lainnya sekarang.

Pilihan lainnya adalah dispatch_after, yang akan mengambil satu blok dan mengantrekannya pada waktu tertentu.

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

});

Atau, seperti yang telah Anda temukan, jika Anda tidak memerlukan penundaan, Anda dapat menggunakan saja - performSelector:withObject:withObject:

Membekukan Lafeer
sumber
Hal yang juga baik tentang pendekatan ini adalah Anda dapat menggunakan __weakuntuk memberikan pengatur waktu yang berpura-pura hanya tautan yang lemah kembali ke diri sendiri - sehingga Anda tidak memperpanjang siklus hidup objek Anda secara artifisial dan misalnya jika performSelector: afterDelay: mempengaruhi sesuatu yang sedikit seperti ekor rekursi (meskipun tanpa rekursi) kemudian menyelesaikan siklus penahanan.
Tommy
1
Ya, ini harus menjadi jawaban yang diterima. Ini lebih tepat dan lurus ke depan.
Roohul
7

Opsi paling sederhana adalah mengubah metode Anda untuk mengambil satu parameter yang berisi kedua argumen, seperti NSArrayatau NSDictionary(atau menambahkan metode kedua yang mengambil satu parameter, membukanya, dan memanggil metode pertama, lalu memanggil metode kedua pada menunda).

Misalnya, Anda dapat memiliki sesuatu seperti:

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

Dan kemudian untuk menyebutnya, Anda dapat melakukan:

[self performSelector:@selector(fooOneInput:) 
      withObject:[NSDictionary dictionaryWithObjectsAndKeys: @"first", @"firstParam", @"second", @"secondParam", nil] 
      afterDelay:15.0];
aroth
sumber
Bagaimana jika metode tersebut tidak dapat dimodifikasi, katakanlah itu tinggal di UIKit atau semacamnya? Tidak hanya itu, mengubah metode penggunaan juga NSDictionarykehilangan keamanan tipe. Tidak ideal.
fatuhoku
@fatuhoku - Itu tercakup dalam tanda kurung; "tambahkan metode kedua yang mengambil satu parameter, buka kemasannya, dan panggil metode pertama". Yang bekerja terlepas dari mana metode kehidupan pertama. Adapun keamanan tipe, itu hilang saat keputusan dibuat untuk menggunakan performSelector:(atau NSInvocation). Jika itu masalah, pilihan terbaik mungkin adalah melalui GCD.
sekitar
6
- (void) callFooWithArray: (NSArray *) inputArray
{
    [self fooFirstInput: [inputArray objectAtIndex:0] secondInput: [inputArray objectAtIndex:1]];
}


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

}

dan menyebutnya dengan:

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

Anda dapat menemukan semua jenis metode performSelector: yang disediakan di sini:

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

Ada banyak variasi tetapi tidak ada versi yang membutuhkan banyak objek sekaligus penundaan. Anda harus menyelesaikan argumen Anda dalam NSArray atau 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: 
StilesCrisis
sumber
2

Saya tidak suka cara NSInvocation, terlalu rumit. Jaga agar tetap sederhana dan bersih:

// 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);
BB9z
sumber
Bagus! Ganti 'vc' dengan 'target'
Anton
1

Saya baru saja melakukan beberapa desas-desus dan perlu memanggil metode asli. Apa yang saya lakukan adalah membuat protokol dan memasukkan objek saya ke dalamnya. Cara lain adalah dengan mendefinisikan metode dalam sebuah kategori, tetapi akan membutuhkan penekanan peringatan (#pragma clang diagnostic diabaikan "-Wincomplete-Implementation").

darkfader.dll
sumber
0

Cara sederhana dan dapat digunakan kembali adalah dengan memperluas NSObjectdan menerapkan

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

sesuatu seperti:

- (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];
}
Kappe
sumber
0

Saya hanya akan membuat objek khusus yang menampung semua parameter saya sebagai properti, dan kemudian menggunakan objek tunggal itu sebagai parameter

MobileMon
sumber