NSInvocation for Dummies?


139

วิธีการว่าจะNSInvocationทำงานหรือไม่ มีการแนะนำที่ดีหรือไม่?

ฉันมีปัญหาในการทำความเข้าใจว่าโค้ดต่อไปนี้ (จากCocoa Programming สำหรับ Mac OS X, รุ่นที่ 3 ) ทำงานได้อย่างไร แต่จากนั้นก็สามารถใช้แนวคิดที่เป็นอิสระจากตัวอย่างการสอนได้ รหัส:

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

ฉันได้สิ่งที่พยายามทำ (BTW employeesเป็นคลาสที่NSArrayกำหนดเองPerson)

ในฐานะที่เป็นผู้ชาย. NET ฉันพยายามเชื่อมโยงแนวคิด Obj-C และ Cocoa ที่ไม่คุ้นเคยกับแนวคิด. สิ่งนี้คล้ายกับแนวคิดตัวแทนของ. NET แต่ไม่มีการพิมพ์หรือไม่

นี่ไม่ใช่หนังสือที่ชัดเจน 100% ดังนั้นฉันจึงมองหาบางสิ่งเพิ่มเติมจากผู้เชี่ยวชาญของ Cocoa / Obj-C จริงอีกครั้งโดยมีเป้าหมายที่ฉันเข้าใจแนวคิดพื้นฐานภายใต้ตัวอย่างง่าย ๆ (-ish) ฉันกำลังมองหาที่จะใช้ความรู้อย่างอิสระจนถึงบทที่ 9 ฉันไม่มีปัญหาในการทำเช่นนั้น แต่ตอนนี้ ...

ขอบคุณล่วงหน้า!

คำตอบ:


284

ตามการอ้างอิงระดับ NSInvocation ของ Apple :

An NSInvocationคือข้อความ Objective-C ที่แสดงผลแบบคงที่นั่นคือมันเป็นการกระทำที่กลายเป็นวัตถุ

และในรายละเอียดเพิ่มเติมเล็กน้อย :

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


ตัวอย่างเช่นสมมติว่าคุณต้องการเพิ่มสตริงลงในอาร์เรย์ ปกติแล้วคุณจะส่งaddObject:ข้อความดังต่อไปนี้:

[myArray addObject:myString];

สมมติว่าคุณต้องการใช้NSInvocationเพื่อส่งข้อความนี้ ณ เวลาอื่น:

ครั้งแรกที่คุณจะเตรียมNSInvocationวัตถุสำหรับใช้กับNSMutableArray's addObject:เลือก:

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

ถัดไปคุณจะต้องระบุวัตถุที่จะส่งข้อความไปที่:

[myInvocation setTarget:myArray];

ระบุข้อความที่คุณต้องการส่งถึงวัตถุนั้น:

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

และเติมอาร์กิวเมนต์ใด ๆ สำหรับเมธอดนั้น:

[myInvocation setArgument:&myString atIndex:2];

โปรดทราบว่าอาร์กิวเมนต์ของวัตถุจะต้องผ่านตัวชี้ ขอบคุณRyan McCuaig ที่ชี้ให้เห็นและโปรดดูเอกสารประกอบของ Appleสำหรับรายละเอียดเพิ่มเติม

ณ จุดmyInvocationนี้เป็นวัตถุที่สมบูรณ์อธิบายข้อความที่สามารถส่งได้ ในการส่งข้อความคุณจะต้องโทร:

[myInvocation invoke];

[myArray addObject:myString];นี้ขั้นตอนสุดท้ายจะทำให้ข้อความที่จะส่งเป็นหลักดำเนินการ

คิดเหมือนการส่งอีเมล คุณเปิดอีเมลใหม่ ( NSInvocationวัตถุ) กรอกที่อยู่ของบุคคล (วัตถุ) ที่คุณต้องการส่งไปให้พิมพ์ข้อความสำหรับผู้รับ (ระบุ a selectorและอาร์กิวเมนต์) แล้วคลิก "ส่ง" (โทรinvoke)

ดูการใช้ NSInvocationสำหรับข้อมูลเพิ่มเติม ดูที่การใช้ NSInvocationหากข้อมูลด้านบนไม่ทำงาน


NSUndoManagerใช้NSInvocationวัตถุเพื่อที่จะสามารถย้อนกลับคำสั่ง โดยพื้นฐานแล้วสิ่งที่คุณกำลังทำคือการสร้างNSInvocationวัตถุที่จะพูดว่า: "เฮ้ถ้าคุณต้องการยกเลิกสิ่งที่ฉันเพิ่งทำไปให้ส่งข้อความนี้ไปที่วัตถุนั้นโดยมีข้อโต้แย้งเหล่านี้" คุณมอบNSInvocationวัตถุให้กับNSUndoManagerและเพิ่มวัตถุนั้นลงในอาร์เรย์ของการกระทำที่ไม่สามารถยกเลิกได้ หากผู้ใช้เรียกว่า "เลิกทำ" NSUndoManagerจะค้นหาการกระทำล่าสุดในอาร์เรย์และเรียกใช้NSInvocationวัตถุที่เก็บไว้เพื่อดำเนินการที่จำเป็น

ดูการลงทะเบียนการเลิกทำการดำเนินการสำหรับรายละเอียดเพิ่มเติม


10
หนึ่งการแก้ไขเล็กน้อยสำหรับคำตอบที่ยอดเยี่ยมเป็นอย่างอื่น ... คุณต้องส่งตัวชี้ไปยังวัตถุในsetArgument:atIndex:ดังนั้นการมอบหมายหาเรื่องควรอ่านจริง[myInvocation setArgument:&myString atIndex:2]
ไรอัน McCuaig

60
เพียงเพื่อชี้แจงบันทึกของไรอันดัชนี 0 ถูกสงวนไว้สำหรับ "ตนเอง" และดัชนี 1 สงวนไว้สำหรับ "_cmd" (ดูลิงก์ e.James ที่โพสต์เพื่อดูรายละเอียดเพิ่มเติม) ดังนั้นอาร์กิวเมนต์แรกของคุณจะถูกวางไว้ที่ดัชนี 2, อาร์กิวเมนต์ที่สองที่ดัชนี 3 ฯลฯ ...
เดฟ

4
@haroldcampbell: เราต้องโทรอะไร
e.James

6
ฉันไม่เข้าใจว่าทำไมเราต้องเรียก setSelector เนื่องจากเราได้ระบุตัวเลือกใน mySignature แล้ว
Gleno

6
@Gleno: NSInvocation ค่อนข้างยืดหยุ่น คุณสามารถตั้งค่าตัวเลือกใด ๆ ที่ตรงกับลายเซ็นเมธอดได้ดังนั้นคุณไม่จำเป็นต้องใช้ตัวเลือกเดียวกับที่ใช้สร้างลายเซ็นวิธี ในตัวอย่างนี้คุณสามารถทำได้อย่างง่ายดาย setSelector: @selector (removeObject :) เนื่องจากพวกเขาแบ่งปันลายเซ็นวิธีการเดียวกัน
e.James

48

นี่คือตัวอย่างง่ายๆของการดำเนินการ NSInvocation:

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

เมื่อเรียกว่า - [self hello:@"Hello" world:@"world"];- วิธีการจะ:

  • พิมพ์ "Hello world!"
  • สร้าง NSMethodSignature ด้วยตัวเอง
  • สร้างและเติม NSInvocation เรียกตัวเอง
  • ผ่าน NSInvocation ไปยัง NSTimer
  • ตัวจับเวลาจะเริ่มทำงานใน (ประมาณ) 1 วินาทีทำให้วิธีการถูกเรียกอีกครั้งพร้อมอาร์กิวเมนต์ดั้งเดิม
  • ทำซ้ำ

ในท้ายที่สุดคุณจะได้รับงานพิมพ์ดังนี้:

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!
...

แน่นอนวัตถุเป้าหมายselfจะต้องมีอยู่เพื่อให้ NSTimer ส่ง NSInvocation ไปยังวัตถุนั้น ตัวอย่างเช่นSingletonวัตถุหรือ AppDelegate ซึ่งมีอยู่ในช่วงระยะเวลาของแอปพลิเคชัน


UPDATE:

ตามที่ระบุไว้ข้างต้นเมื่อคุณส่ง NSInvocation เป็นอาร์กิวเมนต์ไปยัง NSTimer NSTimer จะเก็บอาร์กิวเมนต์ NDSIMVocation ทั้งหมดโดยอัตโนมัติ

หากคุณไม่ได้ผ่าน NSInvocation เป็นอาร์กิวเมนต์ให้กับ NSTimer และวางแผนที่จะให้มันติดอยู่ระยะหนึ่งคุณจะต้องเรียก-retainArgumentsวิธีการของมัน มิฉะนั้นอาจมีการยกเลิกการจัดสรรก่อนที่จะมีการเรียกใช้ในที่สุดทำให้รหัสของคุณขัดข้อง นี่คือวิธีการ:

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

6
มันน่าสนใจที่แม้การเริ่มต้นจะใช้คุณยังคงต้องโทรinvocationWithMethodSignature: setSelector:ดูเหมือนซ้ำซ้อน แต่ฉันเพิ่งทดสอบและจำเป็น
ThomasW

สิ่งนี้ยังคงทำงานเป็นวงวนไม่สิ้นสุดหรือไม่? และ _cmd
j2emanue คือ


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