Cách thực hiện Gọi lại trong Objective-C


119

Làm thế nào để thực hiện các chức năng gọi lại trong Objective-C?

Tôi chỉ muốn xem một số ví dụ đã hoàn thành và tôi nên hiểu nó.

Câu trả lời:


94

Thông thường, các lệnh gọi lại trong mục tiêu C được thực hiện với các đại biểu. Đây là một ví dụ về triển khai ủy quyền tùy chỉnh;


Tập tin tiêu đề:

@interface MyClass : NSObject {
    id delegate;
}
- (void)setDelegate:(id)delegate;
- (void)doSomething;
@end

@interface NSObject(MyDelegateMethods)
- (void)myClassWillDoSomething:(MyClass *)myClass;
- (void)myClassDidDoSomething:(MyClass *)myClass;
@end

Thực hiện (.m) Tệp

@implementation MyClass
- (void)setDelegate:(id)aDelegate {
    delegate = aDelegate; /// Not retained
}

- (void)doSomething {
    [delegate myClassWillDoSomething:self];
    /* DO SOMETHING */
    [delegate myClassDidDoSomething:self];
}
@end

Điều đó minh họa cách tiếp cận chung. Bạn tạo một danh mục trên NSObject khai báo tên của các phương thức gọi lại của bạn. NSObject không thực sự triển khai các phương pháp này. Loại danh mục này được gọi là giao thức không chính thức, bạn chỉ đang nói rằng nhiều đối tượng có thể triển khai các phương thức này. Chúng là một cách để chuyển tiếp khai báo kiểu chữ ký của bộ chọn.

Tiếp theo, bạn có một số đối tượng là đại biểu của "MyClass" và MyClass gọi các phương thức đại biểu trên đại biểu nếu thích hợp. Nếu các lệnh gọi lại được ủy quyền của bạn là tùy chọn, thông thường bạn sẽ bảo vệ chúng tại trang điều phối bằng một cái gì đó như "if ([Delegate responseToSelector: @selector (myClassWillDoSomething :)) {". Trong ví dụ của tôi, người được ủy quyền được yêu cầu triển khai cả hai phương pháp.

Thay vì một giao thức không chính thức, bạn cũng có thể sử dụng một giao thức chính thức được xác định bằng @protocol. Nếu bạn làm điều đó, bạn sẽ thay đổi kiểu của trình thiết lập ủy quyền và biến thể hiện thành " id <MyClassDelegate>" thay vì chỉ " id".

Ngoài ra, bạn sẽ nhận thấy người đại diện không được giữ lại. Điều này thường được thực hiện vì đối tượng "sở hữu" các thể hiện của "MyClass" thường cũng là đại biểu. Nếu MyClass giữ lại đại biểu của nó, thì sẽ có một chu kỳ lưu giữ. Đó là một ý tưởng hay trong phương thức dealloc của một lớp có thể hiện MyClass và là đại diện của nó để xóa tham chiếu đại biểu đó vì nó là một con trỏ ngược yếu. Nếu không, nếu có điều gì đó đang giữ cho cá thể MyClass tồn tại, bạn sẽ có một con trỏ treo lơ lửng.


+1 Câu trả lời cặn kẽ tốt. Đóng băng sẽ là một liên kết đến tài liệu chuyên sâu hơn của Apple về các đại biểu. :-)
Quinn Taylor

Jon, cảm ơn rất nhiều vì sự giúp đỡ của bạn. Tôi thực sự đánh giá cao sự giúp đỡ của bạn. Tôi xin lỗi về điều này, nhưng tôi không quá rõ ràng về câu trả lời. Message .m là một lớp tự đặt nó làm đại biểu trong khi gọi hàm doSomething. DoSomething có phải là hàm gọi lại mà người dùng gọi không? vì tôi có ấn tượng rằng người dùng gọi doSomething và các hàm gọi lại của bạn là myClassWillDoSomethingg & myClassDidDoSomething. Ngoài ra, bạn có thể chỉ cho tôi cách tạo một lớp cao hơn gọi hàm call back. Tôi là một lập trình viên C nên tôi không quá quen thuộc với môi trường Obj-C.
ReachConnection

"Message .m" chỉ có nghĩa là, trong tệp .m của bạn. Bạn sẽ có một lớp học riêng, hãy gọi nó là "Foo". Foo sẽ có một biến "MyClass * myClass" và tại một thời điểm nào đó Foo sẽ nói "[myClass setDelegate: self]". Tại bất kỳ thời điểm nào sau đó, nếu bất kỳ ai kể cả foo gọi doSomethingMethod trên phiên bản MyClass đó, foo sẽ có các phương thức myClassWillDoSomething và myClassDidDoSomething được gọi ra. Thực ra tôi cũng sẽ chỉ đăng một ví dụ khác thứ hai không sử dụng các đại biểu.
Jon Hess

Tôi không nghĩ rằng .m là viết tắt của "tin nhắn".
Chuck


140

Để hoàn thiện, vì StackOverflow RSS vừa ngẫu nhiên trả lại câu hỏi cho tôi, nên tùy chọn khác (mới hơn) là sử dụng các khối:

@interface MyClass: NSObject
{
    void (^_completionHandler)(int someParameter);
}

- (void) doSomethingWithCompletionHandler:(void(^)(int))handler;
@end


@implementation MyClass

- (void) doSomethingWithCompletionHandler:(void(^)(int))handler
{
    // NOTE: copying is very important if you'll call the callback asynchronously,
    // even with garbage collection!
    _completionHandler = [handler copy];

    // Do stuff, possibly asynchronously...
    int result = 5 + 3;

    // Call completion handler.
    _completionHandler(result);

    // Clean up.
    [_completionHandler release];
    _completionHandler = nil;
}

@end

...

MyClass *foo = [[MyClass alloc] init];
int x = 2;
[foo doSomethingWithCompletionHandler:^(int result){
    // Prints 10
    NSLog(@"%i", x + result);
}];

2
@Ahruman: Ký tự "^" - trong "void (^ _completionHandler) (int someParameter);" nghĩa là? Bạn có thể giải thích dòng đó làm gì không?
Konrad Höffner

2
Bạn có thể giải thích lý do tại sao bạn cần sao chép trình xử lý gọi lại không?
Elliot Chance

52

Đây là một ví dụ giúp loại bỏ các khái niệm về đại diện và chỉ thực hiện một cuộc gọi thô trở lại.

@interface Foo : NSObject {
}
- (void)doSomethingAndNotifyObject:(id)object withSelector:(SEL)selector;
@end

@interface Bar : NSObject {
}
@end

@implementation Foo
- (void)doSomethingAndNotifyObject:(id)object withSelector:(SEL)selector {
    /* do lots of stuff */
    [object performSelector:selector withObject:self];
}
@end

@implementation Bar
- (void)aMethod {
    Foo *foo = [[[Foo alloc] init] autorelease];
    [foo doSomethingAndNotifyObject:self withSelector:@selector(fooIsDone:)];
}

- (void)fooIsDone:(id)sender {
    NSLog(@"Foo Is Done!");
}
@end

Thông thường, phương thức - [Foo doSomethingAndNotifyObject: withSelector:] sẽ không đồng bộ, điều này sẽ làm cho lệnh gọi lại hữu ích hơn ở đây.


1
Cảm ơn rất nhiều John. Tôi hiểu việc triển khai gọi lại đầu tiên của bạn sau nhận xét của bạn. Ngoài ra, việc triển khai cuộc gọi lại thứ hai của bạn đơn giản hơn. Cả hai đều rất tốt.
ReachConnection

1
Cảm ơn vì đã đăng bài này Jon, nó rất hữu ích. Tôi đã phải thay đổi [object performanceSelectorwithObject: self]; to [object performanceSelector: selector withObject: self]; để làm cho nó hoạt động chính xác.
Banjer

16

Để giữ cho câu hỏi này được cập nhật, việc giới thiệu ARC của iOS 5.0 có nghĩa là điều này có thể đạt được bằng cách sử dụng Blocks ngắn gọn hơn:

@interface Robot: NSObject
+ (void)sayHi:(void(^)(NSString *))callback;
@end

@implementation Robot
+ (void)sayHi:(void(^)(NSString *))callback {
    // Return a message to the callback
    callback(@"Hello to you too!");
}
@end

[Robot sayHi:^(NSString *reply){
  NSLog(@"%@", reply);
}];

Luôn có F **** ng Cú pháp Khối nếu bạn quên cú pháp Khối của Objective-C.


Trong @ interface nên + (void)sayHi:(void(^)(NSString *reply))callback;không+ (void)sayHi:(void(^)(NSString *))callback;
Tim007

Không theo nói trên F **** ng Khối Cú pháp : - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;(Lưu ý parameterTypeskhông parameters)
Ryan Brodie

4

CallBack: Có 4 kiểu gọi lại trong Mục tiêu C

  1. Loại bộ chọn : Bạn có thể xem NSTimer, UIPangesture là các ví dụ về gọi lại Bộ chọn. Được sử dụng để thực thi mã rất hạn chế.

  2. Loại ủy quyền : Phổ biến và được sử dụng nhiều nhất trong Apple framework. UITableViewDelegate, NSNURLConnectionDelegate. Chúng thường được sử dụng để hiển thị Tải xuống nhiều hình ảnh từ máy chủ không đồng bộ, v.v.

  3. NSNotifications : NotificationCenter là một trong những tính năng của Objective C được sử dụng để thông báo cho nhiều người nhận tại thời điểm khi sự kiện xảy ra.
  4. Các khối : Các khối được sử dụng phổ biến hơn trong lập trình Objective C. Nó là một tính năng tuyệt vời và được sử dụng để thực thi đoạn mã. Bạn cũng có thể tham khảo hướng dẫn để hiểu: Khối hướng dẫn

Xin vui lòng cho tôi nếu bất kỳ câu trả lời khác cho nó. Tôi sẽ đánh giá cao nó.

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.