2008-11-24 28 views
133

Chính xác NSInvocation hoạt động như thế nào? Có giới thiệu hay không?NSInvocation for Dummies?

Tôi đặc biệt gặp sự cố khi hiểu mã sau đây (từ Lập trình cacao cho Mac OS X, 3rd Edition) 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ã:

- (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, employees là một NSArray của lớp tùy chỉnh Person.)

Là một .NET, tôi cố gắng kết hợp các khái niệm Obj-C và Cocoa không quen thuộc với khái niệm tương tự. Điều này có tương tự như khái niệm đại biểu của .NET không, nhưng không được nhập?

Đây không phải là 100% rõ ràng từ cuốn sách, vì vậy tôi đang tìm một cái gì đó bổ sung từ các chuyên gia thực sự Cocoa/Obj-C, một lần nữa với mục tiêu là 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ự tìm cách để có thể áp dụng độc lập kiến ​​thức - cho đến chương 9, tôi đã không gặp khó khăn khi làm điều đó. Nhưng bây giờ ...

Xin cảm ơn trước!

Trả lời

269

Theo Apple's NSInvocation class reference:

Một NSInvocation là một thông điệp Objective-C render tĩnh, có nghĩa là, nó là một hành động biến thành một đối tượng.

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

Khái niệm về các thông điệp là trung tâm của triết lý Objective-C. Bất cứ khi nào bạn gọi một phương thức, hoặc truy cập một biến của một số đối tượng, bạn sẽ gửi một thông báo. NSInvocation có í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 hoặc gửi cùng một tin nhắn nhiều lần. NSInvocation cho phép bạn mô tả thư 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 sẽ gửi tin nhắn addObject: như sau:

[myArray addObject:myString]; 

Bây giờ, giả sử bạn muốn sử dụng NSInvocation để gửi thông điệp này tại một số điểm khác trong thời gian:

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ẽ SPE cify mà đối tượng để gửi tin nhắn đến:

[myInvocation setTarget:myArray]; 

Xác định thông điệp mà bạn muốn gửi đến đối tượng đó:

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

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

[myInvocation setArgument:&myString atIndex:2]; 

Lưu ý rằng đối số đối tượng phải được chuyển bởi con trỏ. Cảm ơn bạn Ryan McCuaig vì đã chỉ ra điều đó và vui lòng xem Apple's documentation để biết thêm chi tiết.

Tại thời điểm này, myInvocation là đối tượng hoàn chỉnh, mô tả thư 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ẽ gây ra các thông điệp được gửi, về cơ bản thực hiện [myArray addObject:myString];.

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

Xem Using NSInvocation để biết thêm thông tin. Xem Using NSInvocation nếu thông tin trên không hoạt động.


NSUndoManager sử dụng NSInvocation đối tượng để nó có thể đảo ngược lệnh. Về cơ bản, những gì bạn đang làm là tạo ra một đối tượng NSInvocation để 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 thông điệp này đến đối tượng đó, với những đối số này". Bạn cung cấp đối tượng NSInvocation cho NSUndoManager và nó thêm đối tượng đó vào một loạt các hành động có thể hoàn tác. Nếu người dùng gọi "Hoàn tác", NSUndoManager chỉ cần tra cứu hành động gần đây nhất trong mảng và gọi đối tượng được lưu trữ NSInvocation để thực hiện hành động cần thiết.

Xem Registering Undo Operations để biết thêm chi tiết.

+9

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

+0

@Ryan McCuaig: Cảm ơn bạn đã chỉ ra điều đó. Tôi đã thực hiện thay đổi và thêm liên kết vào tài liệu có liên quan. –

+56

Chỉ cần làm rõ ghi chú của Ryan, chỉ mục 0 được dành riêng cho "tự" và chỉ mục 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 tại chỉ số 2, đối số thứ hai tại chỉ số 3, v.v ... –

45

Dưới đây là một ví dụ đơn giản của NSInvocation trong hành độ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 gọi - [self hello:@"Hello" world:@"world"]; - phương pháp này sẽ:

  • In "Hello world!"
  • Tạo NSMethodSignature cho chính nó.
  • Tạo và điền một NSInvocation, tự gọi.
  • Chuyển NSInvocation sang NSTimer
  • Bộ hẹn giờ sẽ kích hoạt trong khoảng (khoảng) 1 giây, khiến phương thức được gọi lại với các đối số ban đầu của nó.
  • Lặp 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 mục tiêu self phải tiếp tục tồn tại cho NSTimer để gửi NSInvocation với nó. Ví dụ: một đối tượng Singleton hoặc một AppDelegate tồn tại trong suốt thời gian của ứng dụng.


UPDATE:

Như đã đề cập ở trên, khi bạn vượt qua một NSInvocation như một tham số để một NSTimer, các NSTimer tự động giữ lại tất cả các đối số của NSInvocation.

Nếu bạn không chuyển NSInvocation như một đối số cho một NSTimer, và có kế hoạch để nó dính xung quanh một lúc, bạn phải gọi phương thức -retainArguments của nó. Nếu không, các đối số của nó có thể được deallocated trước khi lời gọi được gọi, cuối cùng làm cho mã của bạn bị lỗi. Dưới đâ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

Nó thú vị là mặc dù 'invocationWithMethodSignature:' initializer được sử dụng, bạn vẫn cần phải gọi 'setSelector:'. Nó có vẻ dư thừa, nhưng Tôi chỉ cần kiểm tra và nó là cần thiết – ThomasW

+0

Nó có thể được thực hiện với các phương pháp tĩnh không? – Apollo

+0

Điều này có tiếp tục chạy trong một vòng lặp vô hạn không? Và những gì là _cmd – j2emanue