NSInvocation cho người giả?


139

Làm thế nào chính xác NSInvocationlàm việc? Có một giới thiệu tốt?

Tôi đặc biệt gặp vấn đề khi hiểu cách mã sau (từ Lập trình ca cao cho Mac OS X, Phiên bản thứ 3 ) hoạt động, nhưng sau đó cũng có thể áp dụng các khái niệm độc lập với mẫu hướng dẫn. Mật mã:

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

Tôi hiểu những gì nó đang cố gắng làm. (BTW, employeeslà một lớp NSArraytùy chỉnh Person.)

Là một người .NET, tôi cố gắng liên kết các khái niệm Obj-C và Cacao không quen thuộc với các khái niệm .NET tương tự nhau. Điều này có giống với khái niệm đại biểu của .NET không, nhưng chưa được kiểm tra?

Điều này không rõ ràng 100% từ cuốn sách, vì vậy tôi đang tìm kiếm một cái gì đó bổ sung từ các chuyên gia Cacao / Obj-C thực sự, một lần nữa với mục tiêu tôi hiểu khái niệm cơ bản bên dưới ví dụ đơn giản (-ish). Tôi thực sự mong muốn có thể áp dụng kiến ​​thức một cách độc lập - cho đến chương 9, tôi không gặp khó khăn gì khi làm điều đó. Nhưng bây giờ ...

Cảm ơn trước!

Câu trả lời:


284

Theo tài liệu tham khảo lớp NSInvocation của Apple :

An NSInvocationlà một thông điệp Objective-C được hiển thị tĩnh, nghĩa là nó là một hành động biến thành một đối tượng.

Và, chi tiết hơn một chút :

Khái niệm thông điệp là trung tâm của triết lý khách quan-c. Bất cứ khi nào bạn gọi một phương thức hoặc truy cập vào một biến của một đối tượng nào đó, bạn sẽ gửi cho nó một tin nhắn. NSInvocationtrở nên hữu ích khi bạn muốn gửi tin nhắn đến một đối tượng tại một thời điểm khác nhau hoặc gửi cùng một tin nhắn nhiều lần. NSInvocationcho phép bạn mô tả thông điệp bạn sẽ gửi và sau đó gọi nó (thực sự gửi nó đến đối tượng đích) sau này.


Ví dụ: giả sử bạn muốn thêm một chuỗi vào một mảng. Bạn thường gửi addObject:tin nhắn như sau:

[myArray addObject:myString];

Bây giờ, giả sử bạn muốn sử dụng NSInvocationđể gửi tin nhắn này vào một thời điểm khác:

Trước tiên, bạn sẽ chuẩn bị một NSInvocationđối tượng để sử dụng với NSMutableArray's addObject:selector:

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

Tiếp theo, bạn sẽ chỉ định đối tượng nào sẽ gửi tin nhắn đến:

[myInvocation setTarget:myArray];

Chỉ định tin nhắn bạn muốn gửi cho đối tượng đó:

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

Và điền vào bất kỳ đối số cho phương thức đó:

[myInvocation setArgument:&myString atIndex:2];

Lưu ý rằng các đối số đối tượng phải được truyền bằng con trỏ. Cảm ơn Ryan McCuaig đã chỉ ra điều đó và vui lòng xem tài liệu của Apple để biết thêm chi tiết.

Tại thời điểm này, myInvocationlà một đối tượng hoàn chỉnh, mô tả một thông điệp có thể được gửi. Để thực sự gửi tin nhắn, bạn sẽ gọi:

[myInvocation invoke];

Bước cuối cùng này sẽ khiến tin nhắn được gửi, về cơ bản là thực thi [myArray addObject:myString];.

Hãy nghĩ về nó như gửi một email. Bạn mở một email ( NSInvocationđối tượng) mới, điền địa chỉ của người (đối tượng) bạn muốn gửi đến, nhập tin nhắn cho người nhận (chỉ định a selectorvà đối số), sau đó bấm "gửi" (gọi invoke).

Xem Sử dụng NSInvocation để biết thêm thông tin. Xem Sử dụng NSInvocation nếu cách trên không hoạt động.


NSUndoManagersử dụng NSInvocationcác đối tượng để nó có thể đảo ngược các lệnh. Về cơ bản, những gì bạn đang làm là tạo ra một NSInvocationđối tượng để nói: "Này, nếu bạn muốn hoàn tác những gì tôi vừa làm, hãy gửi tin nhắn này đến đối tượng đó, với những đối số này". Bạn đưa NSInvocationđối tượng cho NSUndoManagervà nó thêm đối tượng đó vào một loạt các hành động không thể hoàn tác. Nếu người dùng gọi "Hoàn tác", NSUndoManagerchỉ cần tra cứu hành động gần đây nhất trong mảng và gọi NSInvocationđối tượng được lưu trữ để thực hiện hành động cần thiết.

Xem Đăng ký hoạt động hoàn tác để biết thêm chi tiết.


10
Một điều chỉnh nhỏ cho một câu trả lời xuất sắc khác ... bạn phải chuyển một con trỏ tới các đối tượng setArgument:atIndex:, vì vậy việc gán arg thực sự nên đọc [myInvocation setArgument:&myString atIndex:2].
Ryan McCuaig

60
Chỉ cần làm rõ ghi chú của Ryan, chỉ số 0 được dành riêng cho "bản thân" và chỉ số 1 được dành riêng cho "_cmd" (xem liên kết e.James được đăng để biết thêm chi tiết). Vì vậy, đối số đầu tiên của bạn được đặt ở chỉ số 2, đối số thứ hai ở chỉ số 3, v.v ...
Dave

4
@haroldcampbell: chúng ta phải gọi là gì?
e.James

6
Tôi không hiểu tại sao chúng ta phải gọi setSelector, vì chúng tôi đã chỉ định bộ chọn trong mySignature.
Gleno

6
@Gleno: NSInvocation khá linh hoạt. Bạn thực sự có thể đặt bất kỳ bộ chọn nào khớp với chữ ký phương thức, do đó bạn không nhất thiết phải sử dụng cùng bộ chọn được sử dụng để tạo chữ ký phương thức. Trong ví dụ này, bạn có thể dễ dàng thực hiện setSelector: @selector (removeObject :), vì chúng có chung chữ ký phương thức.
e.James

48

Đây là một ví dụ đơn giản về NSInvocation đang hoạt động:

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

Khi được gọi - [self hello:@"Hello" world:@"world"];- phương thức sẽ:

  • In "Xin chào thế giới!"
  • Tạo một NSMethodSignature cho chính nó.
  • Tạo và điền vào NSInvocation, gọi chính nó.
  • Truyền NSInvocation cho một NSTimer
  • Bộ định thời sẽ kích hoạt trong (khoảng) 1 giây, khiến phương thức được gọi lại với các đối số ban đầu.
  • Nói lại.

Cuối cùng, bạn sẽ nhận được một bản in như vậy:

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

Tất nhiên, đối tượng đích selfphải tiếp tục tồn tại để NSTimer gửi NSInvocation đến nó. Ví dụ: một đối tượng Singleton hoặc AppDelegate tồn tại trong suốt thời gian của ứng dụng.


CẬP NHẬT:

Như đã lưu ý ở trên, khi bạn chuyển NSInvocation làm đối số cho NSTimer, NSTimer sẽ tự động giữ lại tất cả các đối số của NSInvocation.

Nếu bạn không chuyển NSInvocation làm đối số cho NSTimer và dự định sẽ sử dụng nó trong một thời gian, bạn phải gọi -retainArgumentsphương thức của nó . Mặt khác, các đối số của nó có thể được giải quyết trước khi gọi ra, cuối cùng làm cho mã của bạn bị sập. Đây là cách thực hiện:

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
Điều thú vị là mặc dù trình invocationWithMethodSignature:khởi tạo được sử dụng, bạn vẫn cần gọi setSelector:. Có vẻ dư thừa, nhưng tôi chỉ thử nghiệm và nó là cần thiết.
ThomasW

Điều này tiếp tục chạy trong một vòng lặp vô hạn? và _cmd là gì
j2emanue 28/03/2015


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.