NSInvokasi untuk Dummies?

139

Bagaimana tepatnya cara NSInvocationkerjanya? Apakah ada pengantar yang bagus?

Saya secara khusus mengalami masalah memahami bagaimana kode berikut (dari Pemrograman Kakao untuk Mac OS X, Edisi ke-3 ) bekerja, tetapi kemudian juga dapat menerapkan konsep-konsep secara independen dari sampel tutorial. Kode:

- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index
{
    NSLog(@"adding %@ to %@", p, employees);
    // Add inverse of this operation to undo stack
    NSUndoManager *undo = [self undoManager];
    [[undo prepareWithInvocationTarget:self] removeObjectFromEmployeesAtIndex:index];
    if (![undo isUndoing])
        [undo setActionName:@"Insert Person"];

    // Finally, add person to the array
    [employees insertObject:p atIndex:index];
}

- (void)removeObjectFromEmployeesAtIndex:(int)index
{
    Person *p = [employees objectAtIndex:index];
    NSLog(@"removing %@ from %@", p, employees);
    // Add inverse of this operation to undo stack
    NSUndoManager *undo = [self undoManager];
    [[undo prepareWithInvocationTarget:self] insertObject:p
                                       inEmployeesAtIndex:index];
    if (![undo isUndoing])
        [undo setActionName:@"Delete Person"];

    // Finally, remove person from array
    [employees removeObjectAtIndex:index];
}

Saya mendapatkan apa yang ingin dilakukan. (BTW, employeesadalah kelas NSArraykhusus Person.)

Menjadi seorang. NET guy, saya mencoba mengaitkan konsep Obj-C dan Cocoa yang tidak dikenal dengan konsep .NET. Apakah ini mirip dengan konsep delegasi .NET, tetapi tidak diketik?

Ini tidak 100% jelas dari buku ini, jadi saya mencari sesuatu tambahan dari pakar Kakao / Obj-C yang asli, lagi-lagi dengan tujuan agar saya memahami konsep dasar di bawah contoh sederhana (-ish). Saya benar-benar ingin dapat mengaplikasikan pengetahuan secara mandiri - sampai bab 9, saya tidak mengalami kesulitan melakukan itu. Tapi sekarang ...

Terima kasih sebelumnya!

John Rudy
sumber

Jawaban:

284

Menurut referensi kelas NSInvocation Apple :

Sebuah NSInvocationadalah diberikan statis pesan Objective-C, yaitu, itu adalah tindakan berubah menjadi obyek.

Dan, sedikit lebih detail:

Konsep pesan adalah inti dari filosofi tujuan-c. Setiap kali Anda memanggil suatu metode, atau mengakses variabel dari suatu objek, Anda mengirimkannya sebuah pesan. NSInvocationberguna ketika Anda ingin mengirim pesan ke objek pada waktu yang berbeda, atau mengirim pesan yang sama beberapa kali. NSInvocationmemungkinkan Anda untuk menggambarkan pesan yang akan Anda kirim, dan kemudian memintanya (sebenarnya mengirimnya ke objek target) nanti.


Misalnya, katakanlah Anda ingin menambahkan string ke array. Anda biasanya mengirim addObject:pesan sebagai berikut:

[myArray addObject:myString];

Sekarang, katakanlah Anda ingin menggunakannya NSInvocationuntuk mengirim pesan ini di waktu lain:

Pertama, Anda akan mempersiapkan NSInvocationobjek untuk digunakan dengan NSMutableArray's addObject:pemilih:

NSMethodSignature * mySignature = [NSMutableArray
    instanceMethodSignatureForSelector:@selector(addObject:)];
NSInvocation * myInvocation = [NSInvocation
    invocationWithMethodSignature:mySignature];

Selanjutnya, Anda akan menentukan objek yang akan dikirimi pesan:

[myInvocation setTarget:myArray];

Tentukan pesan yang ingin Anda kirim ke objek itu:

[myInvocation setSelector:@selector(addObject:)];

Dan isi argumen apa pun untuk metode itu:

[myInvocation setArgument:&myString atIndex:2];

Perhatikan bahwa argumen objek harus dilewati oleh pointer. Terima kasih kepada Ryan McCuaig untuk menunjukkan hal itu, dan silakan lihat dokumentasi Apple untuk lebih jelasnya.

Pada titik ini, myInvocationadalah objek yang lengkap, menggambarkan pesan yang dapat dikirim. Untuk benar-benar mengirim pesan, Anda akan menelepon:

[myInvocation invoke];

Langkah terakhir ini akan menyebabkan pesan dikirim, pada dasarnya mengeksekusi [myArray addObject:myString];.

Anggap saja seperti mengirim email. Anda membuka email baru ( NSInvocationobjek), mengisi alamat orang (objek) yang ingin Anda kirimi, ketik pesan untuk penerima (tentukan a selectordan argumen), lalu klik "kirim" (panggilan) invoke).

Lihat Menggunakan NSInvocation untuk informasi lebih lanjut. Lihat Menggunakan NSInvocation jika hal di atas tidak berfungsi.


NSUndoManagermenggunakan NSInvocationobjek sehingga dapat membalikkan perintah. Pada dasarnya, apa yang Anda lakukan adalah membuat NSInvocationobjek untuk mengatakan: "Hei, jika Anda ingin membatalkan apa yang baru saja saya lakukan, kirim pesan ini ke objek itu, dengan argumen ini". Anda memberikan NSInvocationobjek ke NSUndoManager, dan menambahkan objek ke array tindakan yang tidak dapat diurungkan. Jika pengguna memanggil "Undo", NSUndoManagercukup cari tindakan terbaru dalam array, dan aktifkan yang tersimpanNSInvocation objek yang untuk melakukan tindakan yang diperlukan.

Lihat Mendaftar Membatalkan Operasi untuk detail lebih lanjut.

e.James
sumber
10
Satu koreksi kecil untuk jawaban yang sangat baik ... Anda harus meneruskan pointer ke objek setArgument:atIndex:, sehingga tugas arg harus benar-benar membaca [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig
60
Hanya untuk memperjelas catatan Ryan, indeks 0 dicadangkan untuk "diri" dan indeks 1 dicadangkan untuk "_cmd" (lihat tautan e.James yang diposting untuk detail lebih lanjut). Jadi argumen pertama Anda ditempatkan pada indeks 2, argumen kedua pada indeks 3, dll ...
Dave
4
@haroldcampbell: apa yang harus kita panggil?
e.James
6
Saya tidak mengerti mengapa kita harus memanggil setSelector, karena kita sudah menentukan pemilih di mySignature.
Gleno
6
@ Gleno: NSInvocation cukup fleksibel. Anda benar-benar dapat mengatur pemilih yang cocok dengan tanda tangan metode, jadi Anda tidak harus menggunakan pemilih yang sama yang digunakan untuk membuat tanda tangan metode. Dalam contoh ini, Anda bisa dengan mudah melakukan setSelector: @selector (removeObject :), karena mereka berbagi metode tanda tangan yang sama.
e.James
48

Berikut adalah contoh sederhana dari NSInvocation in action:

- (void)hello:(NSString *)hello world:(NSString *)world
{
    NSLog(@"%@ %@!", hello, world);

    NSMethodSignature *signature  = [self methodSignatureForSelector:_cmd];
    NSInvocation      *invocation = [NSInvocation invocationWithMethodSignature:signature];

    [invocation setTarget:self];                    // index 0 (hidden)
    [invocation setSelector:_cmd];                  // index 1 (hidden)
    [invocation setArgument:&hello atIndex:2];      // index 2
    [invocation setArgument:&world atIndex:3];      // index 3

    // NSTimer's always retain invocation arguments due to their firing delay. Release will occur when the timer invalidates itself.
    [NSTimer scheduledTimerWithTimeInterval:1 invocation:invocation repeats:NO];
}

Ketika dipanggil - [self hello:@"Hello" world:@"world"];- metode akan:

  • Cetak "Halo dunia!"
  • Buat NSMethodSignature untuk dirinya sendiri.
  • Buat dan isi NSInvocation, panggil sendiri.
  • Lewati NSInvocation ke NSTimer
  • Timer akan menyala dalam waktu (sekitar) 1 detik, menyebabkan metode dipanggil lagi dengan argumen aslinya.
  • Ulang.

Pada akhirnya, Anda akan mendapatkan hasil cetakan seperti:

2010-07-11 17:48:45.262 Your App[2523:a0f] Hello world!
2010-07-11 17:48:46.266 Your App[2523:a0f] Hello world!
2010-07-11 17:48:47.266 Your App[2523:a0f] Hello world!
2010-07-11 17:48:48.267 Your App[2523:a0f] Hello world!
2010-07-11 17:48:49.268 Your App[2523:a0f] Hello world!
2010-07-11 17:48:50.268 Your App[2523:a0f] Hello world!
2010-07-11 17:48:51.269 Your App[2523:a0f] Hello world!
...

Tentu saja, objek target selfharus tetap ada untuk NSTimer untuk mengirim NSInvocation ke sana. Misalnya, objek Singleton , atau AppDelegate yang ada selama aplikasi.


MEMPERBARUI:

Seperti disebutkan di atas, ketika Anda melewatkan NSInvocation sebagai argumen ke NSTimer, NSTimer secara otomatis mempertahankan semua argumen NSInvocation.

Jika Anda tidak meneruskan NSInvocation sebagai argumen ke NSTimer, dan berencana untuk menggunakannya sebentar, Anda harus memanggil -retainArgumentsmetodenya. Kalau tidak, argumennya dapat dibatalkan alokasi sebelum pemanggilan dipanggil, akhirnya menyebabkan kode Anda mogok. Inilah cara melakukannya:

NSMethodSignature *signature  = ...;
NSInvocation      *invocation = [NSInvocation invocationWithMethodSignature:signature];
id                arg1        = ...;
id                arg2        = ...;

[invocation setTarget:...];
[invocation setSelector:...];
[invocation setArgument:&arg1 atIndex:2];
[invocation setArgument:&arg2 atIndex:3];

[invocation retainArguments];  // If you do not call this, arg1 and arg2 might be deallocated.

[self someMethodThatInvokesYourInvocationEventually:invocation];
Dave
sumber
6
Sangat menarik bahwa meskipun invocationWithMethodSignature:initializer digunakan, Anda masih perlu menelepon setSelector:. Tampaknya berlebihan, tetapi saya baru saja menguji dan itu perlu.
ThomasW
Apakah ini terus berjalan dalam infinite loop? dan apa itu _cmd
j2emanue
0

Saya membangun contoh sederhana memanggil berbagai tipe metode dengan NSInvocation.

Saya mengalami masalah memanggil beberapa param menggunakan obj_msgSend

https://github.com/clearbrian/NSInvocation_Runtime

brian.clear
sumber