2011-01-28 37 views
33

Hãy nói rằng tôi cần phải giao tiếp với một lớp mà cung cấp một giao thức và các cuộc gọi phương pháp đại biểu khi phẫu thuật xong, như vậy:Làm thế nào để đơn giản hóa logic gọi lại với một khối?

@protocol SomeObjectDelegate 

@required 
- (void)stuffDone:(id)anObject; 
- (void)stuffFailed; 

@end 

@interface SomeObject : NSObject 
{ 
} 
@end 

Bây giờ, tôi đã quyết định rằng trong khi tôi thể làm cho lớp khác thực hiện phương thức ủy nhiệm stuffDone:, tôi đã quyết định rằng tôi muốn đóng gói quá trình vào một khối được viết ở đâu đó gần nơi mà SomeObject được khởi tạo, được gọi, v.v. Làm cách nào để tôi thực hiện việc này? Hay nói cách khác, nếu bạn nhìn vào this bài viết nổi tiếng về các khối (trong phần Thay thế cuộc gọi lại); làm thế nào tôi có thể viết một phương thức trong SomeObject chấp nhận một loại completionHandler:?

Trả lời

42

Có vẻ như bạn muốn liên lạc với một lớp tồn tại mà được thiết kế để có một đối tượng đại biểu. Có một số cách tiếp cận, bao gồm:

  1. sử dụng danh mục để thêm các biến thể dựa trên khối của phương pháp thích hợp;
  2. sử dụng lớp dẫn xuất để thêm các biến thể dựa trên khối; và
  3. viết một lớp thực hiện giao thức và gọi các khối của bạn.

Dưới đây là một cách để thực hiện (3). Trước tiên, hãy giả sử SomeObject của bạn là:

@protocol SomeObjectDelegate 
@required 
- (void)stuffDone:(id)anObject; 
- (void)stuffFailed; 

@end 

@interface SomeObject : NSObject 
{ 
} 

+ (void) testCallback:(id<SomeObjectDelegate>)delegate; 

@end 

@implementation SomeObject 

+ (void) testCallback:(id<SomeObjectDelegate>)delegate 
{ 
    [delegate stuffDone:[NSNumber numberWithInt:42]]; 
    [delegate stuffFailed]; 
} 

@end 

vì vậy chúng tôi có một số cách để kiểm tra - bạn sẽ có một SomeObject thực.

Bây giờ xác định một lớp mà thực hiện giao thức và kêu gọi khối cung cấp của bạn:

#import "SomeObject.h" 

typedef void (^StuffDoneBlock)(id anObject); 
typedef void (^StuffFailedBlock)(); 

@interface SomeObjectBlockDelegate : NSObject<SomeObjectDelegate> 
{ 
    StuffDoneBlock stuffDoneCallback; 
    StuffFailedBlock stuffFailedCallback; 
} 

- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail; 
- (void)dealloc; 

+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail; 

// protocol 
- (void)stuffDone:(id)anObject; 
- (void)stuffFailed; 

@end 

lớp này giúp tiết kiệm các khối bạn vượt qua trong và gọi họ là để đáp ứng với callbacks giao thức. Việc thực hiện rất đơn giản:

@implementation SomeObjectBlockDelegate 

- (id) initWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail 
{ 
    if (self = [super init]) 
    { 
     // copy blocks onto heap 
     stuffDoneCallback = Block_copy(done); 
     stuffFailedCallback = Block_copy(fail); 
    } 
    return self; 
} 

- (void)dealloc 
{ 
    Block_release(stuffDoneCallback); 
    Block_release(stuffFailedCallback); 
    [super dealloc]; 
} 

+ (SomeObjectBlockDelegate *) someObjectBlockDelegateWithOnDone:(StuffDoneBlock)done andOnFail:(StuffFailedBlock)fail 
{ 
    return (SomeObjectBlockDelegate *)[[[SomeObjectBlockDelegate alloc] initWithOnDone:done andOnFail:fail] autorelease]; 
} 

// protocol 
- (void)stuffDone:(id)anObject 
{ 
    stuffDoneCallback(anObject); 
} 

- (void)stuffFailed 
{ 
    stuffFailedCallback(); 
} 

@end 

Điều duy nhất bạn cần phải nhớ là để Block_copy() các khối khi khởi tạo và Block_release() chúng sau này - điều này là do khối được stack giao và đối tượng của bạn có thể sống lâu hơn việc tạo của nó ngăn xếp khung; Block_copy() tạo một bản sao trong heap.

Bây giờ bạn có thể tất cả các phương pháp đại biểu dựa trên đi qua nó khối:

[SomeObject testCallback:[SomeObjectBlockDelegate 
            someObjectBlockDelegateWithOnDone:^(id anObject) { NSLog(@"Done: %@", anObject); } 
            andOnFail:^{ NSLog(@"Failed"); } 
            ] 
]; 

Bạn có thể sử dụng kỹ thuật này để quấn khối đối với bất kỳ giao thức.

ARC Phụ Lục

Để đối phó với những nhận xét: để làm cho ARC này tương thích chỉ loại bỏ các cuộc gọi đến Block_copy() rời khỏi nhiệm vụ trực tiếp:

stuffDoneCallback = done; 
stuffFailedCallback = fail; 

và loại bỏ các phương pháp dealloc. Bạn cũng có thể thay đổi Blockcopy thành copy, tức là stuffDoneCallback = [done copy]; và đây là những gì bạn có thể giả định là cần thiết để đọc tài liệu ARC. Tuy nhiên nó không phải là nhiệm vụ là một biến mạnh khiến ARC giữ lại giá trị được gán - và giữ lại một khối ngăn xếp sao chép nó vào đống. Do đó, mã ARC được tạo ra có cùng kết quả có hoặc không có copy.

+0

bạn có thể giải thích phần này với ARC không? stuffDoneCallback = Block_copy (đã hoàn thành); stuffFailedCallback = Block_copy (không thành công); Nó nói với tôi, rằng nó cần một cây cầu đúc. – zeiteisen

+0

@zeiteisen - Câu hỏi hay. Bạn có thể biết câu trả lời ngay bây giờ, xin lỗi tôi chưa bao giờ thấy nhận xét. Nhưng đối với khách truy cập trong tương lai tôi đã thêm một phụ lục. – CRD

7

Bạn có thể làm một cái gì đó như thế này:

typedef void (^AZCallback)(NSError *); 

AZCallback callback = ^(NSError *error) { 
    if (error == nil) { 
    NSLog(@"succeeded!"); 
    } else { 
    NSLog(@"failed: %@", error); 
    } 
}; 

SomeObject *o = [[SomeObject alloc] init]; 
[o setCallback:callback]; // you *MUST* -copy the block 
[o doStuff]; 
...etc; 

Sau đó bên SomeObject, bạn có thể làm:

if ([self hadError]) { 
    callback([self error]); 
} else { 
    callback(nil); 
} 
+0

Bạn có thể giải thích tại sao chúng ta nên sao chép khối không? –

1

Liên kết dưới đây giải thích cách gọi lại bằng cách sử dụng đại biểu có thể dễ dàng được thay thế bằng khối.

Các ví dụ bao gồm UITableview, UIAlertview và ModalViewController.

click me

Hy vọng điều này sẽ hữu ích.

+2

Hãy xem http://stackoverflow.com/help/how-to-answer. Cụ thể, câu trả lời này có thể được cải thiện bằng cách làm theo hướng dẫn này: "Luôn trích dẫn phần có liên quan nhất của một liên kết quan trọng, trong trường hợp trang web mục tiêu không thể truy cập hoặc vĩnh viễn ngoại tuyến". –

Các vấn đề liên quan