thành công: / fail: blocks vs hoàn thành: block


23

Tôi thấy hai mẫu chung cho các khối trong Objective-C. Một là một cặp thành công: / fail: blocks, hai là một hoàn thành duy nhất: block.

Ví dụ: giả sử tôi có một tác vụ sẽ trả về một đối tượng không đồng bộ và tác vụ đó có thể thất bại. Mẫu đầu tiên là -taskWithSuccess:(void (^)(id object))success failure:(void (^)(NSError *error))failure. Mẫu thứ hai là -taskWithCompletion:(void (^)(id object, NSError *error))completion.

thành công thất bại:

[target taskWithSuccess:^(id object) {
    // W00t! I've got my object
} failure:^(NSError *error) {
    // Oh noes! report the failure.
}];

hoàn thành:

[target taskWithCompletion:^(id object, NSError *error) {
    if (object) {
        // W00t! I've got my object
    } else {
        // Oh noes! report the failure.
    }
}];

Đó là mô hình ưa thích? Điểm mạnh và điểm yếu là gì? Khi nào bạn sẽ sử dụng cái này hơn cái kia?


Tôi khá chắc chắn Objective-C có xử lý ngoại lệ với ném / bắt, có lý do gì bạn không thể sử dụng nó không?
Thất vọngWithFormsDesigner

Một trong những điều này cho phép xâu chuỗi các cuộc gọi không đồng bộ, ngoại lệ không cung cấp cho bạn.
Frank Shearar

5
@FrustratedWithFormsDesigner: stackoverflow.com/a/3678556/2289 - objc thành ngữ không sử dụng thử / bắt để kiểm soát luồng.
Kiến

1
Vui lòng xem xét việc chuyển Câu trả lời của bạn từ câu hỏi sang câu trả lời ... sau tất cả, đó là câu trả lời (và bạn có thể trả lời câu hỏi của chính mình).

1
Cuối cùng tôi đã chuyển sang áp lực ngang hàng và chuyển câu trả lời của tôi đến một câu trả lời thực tế.
Jeffery Thomas

Câu trả lời:


8

Gọi lại hoàn thành (trái ngược với cặp thành công / thất bại) là chung chung hơn. Nếu bạn cần chuẩn bị một số ngữ cảnh trước khi xử lý trạng thái trả về, bạn có thể thực hiện nó ngay trước mệnh đề "if (object)". Trong trường hợp thành công / thất bại, bạn phải sao chép mã này. Điều này phụ thuộc vào ngữ nghĩa gọi lại, tất nhiên.


Không thể nhận xét về câu hỏi ban đầu ... Các ngoại lệ không phải là kiểm soát luồng hợp lệ trong mục tiêu-c (tốt, ca cao) và không nên được sử dụng như vậy. Ngoại lệ ném chỉ nên được bắt để chấm dứt duyên dáng.

Vâng tôi có thể hiểu. Nếu -task…có thể trả về đối tượng, nhưng đối tượng không ở trạng thái chính xác, thì bạn vẫn sẽ cần xử lý lỗi trong điều kiện thành công.
Jeffery Thomas

Vâng, và nếu khối không đúng vị trí, nhưng được chuyển làm đối số cho bộ điều khiển của bạn, bạn phải ném hai khối xung quanh. Điều này có thể nhàm chán khi gọi lại cần phải được chuyển qua nhiều lớp. Bạn luôn luôn có thể phân chia / soạn lại nó.

Tôi không hiểu làm thế nào xử lý hoàn thành là chung chung hơn. Hoàn thành về cơ bản biến nhiều tham số phương thức thành một - dưới dạng tham số khối. Ngoài ra, chung chung có nghĩa là tốt hơn? Trong MVC, bạn thường có mã trùng lặp trong trình điều khiển xem, đó là một điều ác cần thiết do tách các mối quan tâm. Tôi không nghĩ đó là một lý do để tránh xa MVC.
Boon

@Boon Một lý do tôi thấy trình xử lý đơn là chung chung hơn là cho các trường hợp bạn muốn sử dụng callee / handler / block để xác định xem một thao tác thành công hay thất bại. Xem xét các trường hợp thành công một phần trong đó bạn có thể có một đối tượng có dữ liệu một phần và đối tượng lỗi của bạn là một lỗi cho biết rằng không phải tất cả dữ liệu đã được trả về. Khối có thể tự kiểm tra dữ liệu và kiểm tra xem nó có đủ không. Điều này là không thể với kịch bản gọi lại thành công / thất bại nhị phân.
Travis

8

Tôi có thể nói, liệu API cung cấp một trình xử lý hoàn thành hay một cặp khối thành công / thất bại, chủ yếu là vấn đề sở thích cá nhân.

Cả hai phương pháp đều có ưu và nhược điểm, mặc dù chỉ có sự khác biệt nhỏ.

Hãy xem xét rằng cũng có các biến thể khác, ví dụ trong đó một trình xử lý hoàn thành có thể chỉ có một tham số kết hợp kết quả cuối cùng hoặc một lỗi tiềm ẩn:

typedef void (^completion_t)(id result);

- (void) taskWithCompletion:(completion_t)completionHandler;

[self taskWithCompletion:^(id result){
    if ([result isKindOfError:[NSError class]) {
        NSLog(@"Error: %@", result);
    }
    else {
        ...
    }
}]; 

Mục đích của chữ ký này là một trình xử lý hoàn thành có thể được sử dụng rộng rãi trong các API khác.

Ví dụ, trong Danh mục cho NSArray, có một phương thức forEachApplyTask:completion:gọi tuần tự một tác vụ cho từng đối tượng và phá vỡ vòng lặp IFF có lỗi. Vì phương thức này cũng không đồng bộ, nên nó cũng có một trình xử lý hoàn thành:

typedef void (^completion_t)(id result);
typedef void (^task_t)(id input, completion_t);
- (void) forEachApplyTask:(task_t)task completion:(completion_t);

Trong thực tế, completion_tnhư được định nghĩa ở trên là đủ chung và đủ để xử lý tất cả các kịch bản.

Tuy nhiên, có một số phương tiện khác để tác vụ không đồng bộ báo hiệu thông báo hoàn thành của nó đến trang web cuộc gọi:

Hứa

Hứa hẹn, còn được gọi là Fut Futures, Thời gian bị trì hoãn, hay bị trì hoãn và đại diện cho kết quả cuối cùng của một nhiệm vụ không đồng bộ (xem thêm: Tương lai và lời hứa của wiki ).

Ban đầu, một lời hứa là trong trạng thái chờ đợi của Wap. Đó là, giá trị của nó vẫn chưa được đánh giá và chưa có sẵn.

Trong Objective-C, Promise sẽ là một đối tượng bình thường sẽ được trả về từ một phương thức không đồng bộ như dưới đây:

- (Promise*) doSomethingAsync;

! Trạng thái ban đầu của một Promise là đang chờ xử lý.

Trong khi đó, các tác vụ không đồng bộ bắt đầu đánh giá kết quả của nó.

Cũng lưu ý rằng không có xử lý hoàn thành. Thay vào đó, Promise sẽ cung cấp một phương tiện mạnh mẽ hơn trong đó trang web cuộc gọi có thể nhận được kết quả cuối cùng của tác vụ không đồng bộ, mà chúng ta sẽ thấy sớm.

Nhiệm vụ không đồng bộ, tạo ra đối tượng lời hứa, cuối cùng PHẢI giải quyết lời hứa của nó. Điều đó có nghĩa là, vì một nhiệm vụ có thể thành công hoặc thất bại, nó PHẢI thực hiện một lời hứa thông qua kết quả được đánh giá, hoặc nó PHẢI từ chối lời hứa đưa ra một lỗi cho thấy lý do thất bại.

! Một nhiệm vụ cuối cùng phải giải quyết lời hứa của nó.

Khi một Lời hứa đã được giải quyết, nó không thể thay đổi trạng thái nữa, bao gồm cả giá trị của nó.

! Một lời hứa chỉ có thể được giải quyết một lần .

Khi một lời hứa đã được giải quyết, một trang web cuộc gọi có thể nhận được kết quả (cho dù thất bại hay thành công). Làm thế nào điều này được thực hiện tùy thuộc vào việc lời hứa được thực hiện bằng cách sử dụng kiểu đồng bộ hay kiểu không đồng bộ.

Một lời hứa có thể được thực hiện theo kiểu đồng bộ hoặc không đồng bộ dẫn đến việc chặn ngữ nghĩa không chặn tương ứng .

Theo kiểu đồng bộ để lấy giá trị của lời hứa, một trang gọi sẽ sử dụng một phương thức sẽ chặn luồng hiện tại cho đến khi lời hứa được giải quyết bằng tác vụ không đồng bộ và kết quả cuối cùng có sẵn.

Theo kiểu không đồng bộ, trang web cuộc gọi sẽ đăng ký các cuộc gọi lại hoặc các khối xử lý được gọi ngay sau khi lời hứa đã được giải quyết.

Hóa ra, phong cách đồng bộ có một số nhược điểm đáng kể giúp đánh bại hiệu quả các ưu điểm của các tác vụ không đồng bộ. Có thể đọc một bài viết thú vị về việc triển khai hiện tại của thiếu sót Futures trong phiên bản lib C ++ 11 tiêu chuẩn tại đây: Hứa hẹn tương lai C ++ 0x tương lai .

Làm thế nào, trong Mục tiêu-C, một trang web cuộc gọi sẽ có được kết quả như thế nào?

Chà, có lẽ tốt nhất là đưa ra một vài ví dụ. Có một vài thư viện triển khai Promise (xem các liên kết bên dưới).

Tuy nhiên, đối với các đoạn mã tiếp theo, tôi sẽ sử dụng một triển khai cụ thể của thư viện Promise, có sẵn trên GitHub RXPromise . Tôi là tác giả của RXPromise.

Các triển khai khác có thể có API tương tự, nhưng có thể có sự khác biệt nhỏ và có thể tinh tế trong cú pháp. RXPromise là phiên bản Objective-C của đặc tả Promise / A + , định nghĩa một tiêu chuẩn mở để triển khai các lời hứa mạnh mẽ và có thể tương tác trong JavaScript.

Tất cả các thư viện hứa được liệt kê dưới đây thực hiện kiểu không đồng bộ.

Có sự khác biệt khá đáng kể giữa các triển khai khác nhau. RXPromise sử dụng nội bộ lib lib, hoàn toàn an toàn, cực kỳ nhẹ và cũng cung cấp một số tính năng hữu ích bổ sung, như hủy bỏ.

Một trang web cuộc gọi có được kết quả cuối cùng của nhiệm vụ không đồng bộ thông qua các trình xử lý đăng ký của Đăng ký. Đặc điểm kỹ thuật của Promise / A +, xác định phương thức then.

Phương pháp then

Với RXPromise, nó trông như sau:

promise.then(successHandler, errorHandler);

trong đó thành côngHandler là một khối được gọi khi lời hứa đã được thực hiện, và lỗiHandler là một khối được gọi khi lời hứa đã bị từ chối.

! thenđược sử dụng để có được kết quả cuối cùng và để xác định thành công hoặc xử lý lỗi.

Trong RXPromise, các khối xử lý có chữ ký sau:

typedef id (^success_handler_t)(id result);
typedef id (^error_handler_t)(NSError* error);

Thành công_handler có kết quả tham số rõ ràng là kết quả cuối cùng của tác vụ không đồng bộ. Tương tự, error_handler có lỗi tham số là lỗi được báo cáo bởi tác vụ không đồng bộ khi không thành công.

Cả hai khối có giá trị trả về. Giá trị trả về này là gì, sẽ sớm trở nên rõ ràng.

Trong RXPromise, thenlà một thuộc tính trả về một khối. Khối này có hai tham số, khối xử lý thành công và khối xử lý lỗi. Các trình xử lý phải được xác định bởi trang web cuộc gọi.

! Các trình xử lý phải được xác định bởi trang web cuộc gọi.

Vì vậy, biểu thức promise.then(success_handler, error_handler);là một dạng ngắn của

then_block_t block promise.then;
block(success_handler, error_handler);

Chúng tôi có thể viết mã ngắn gọn hơn nữa:

doSomethingAsync
.then(^id(id result){
    
    return @“OK”;
}, nil);

Đoạn mã có nội dung: Thực thi doS SomethingAsync, khi thành công, sau đó thực thi trình xử lý thành công.

Ở đây, trình xử lý lỗi nilcó nghĩa là, trong trường hợp có lỗi, nó sẽ không được xử lý trong lời hứa này.

Một thực tế quan trọng khác là việc gọi khối được trả lại từ tài sản thensẽ trả lại một Lời hứa:

! then(...)trả lại một lời hứa

Khi gọi khối được trả về từ tài sản then, máy thu nhận ra, máy tính trả về một lời hứa mới , một lời hứa con . Người nhận trở thành lời hứa của cha mẹ .

RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);

Điều đó nghĩa là gì?

Chà, do điều này, chúng ta có thể thực hiện các nhiệm vụ không đồng bộ của chuỗi Chain mà thực hiện tuần tự một cách hiệu quả.

Hơn nữa, giá trị trả về của một trong hai bộ xử lý sẽ trở thành giá trị của bản gốc của lời hứa được trả lại. Vì vậy, nếu nhiệm vụ thành công với kết quả cuối cùng @ lâm OK, thì lời hứa được trả lại sẽ được giải quyết (đã được thực hiện) (có nghĩa là đã hoàn thành).

RXPromise* returnedPromise = asyncA().then(^id(id result){
    return @"OK";
}, nil);

...
assert([[returnedPromise get] isEqualToString:@"OK"]);

Tương tự như vậy, khi tác vụ không đồng bộ thất bại, lời hứa được trả lại sẽ được giải quyết (đó là lỗi bị từ chối).

RXPromise* returnedPromise = asyncA().then(nil, ^id(NSError* error){
    return error;
});

...
assert([[returnedPromise get] isKindOfClass:[NSError class]]);

Người xử lý cũng có thể trả lại một lời hứa khác. Ví dụ khi trình xử lý đó thực thi một tác vụ không đồng bộ khác. Với cơ chế này, chúng ta có thể các chuỗi nhiệm vụ không đồng bộ của chuỗi:

RXPromise* returnedPromise = asyncA().then(^id(id result){
    return asyncB(result);
}, nil);

! Giá trị trả về của một khối xử lý trở thành giá trị của lời hứa con.

Nếu không có lời hứa con, giá trị trả lại không có hiệu lực.

Một ví dụ phức tạp hơn:

Ở đây, chúng tôi thực hiện asyncTaskA, asyncTaskB, asyncTaskCasyncTaskD tuần tự - và mỗi nhiệm vụ tiếp theo sẽ đưa kết quả của nhiệm vụ trước khi đầu vào:

asyncTaskA()
.then(^id(id result){
    return asyncTaskB(result);
}, nil)
.then(^id(id result){
    return asyncTaskC(result);
}, nil)
.then(^id(id result){
    return asyncTaskD(result);
}, nil)
.then(^id(id result){
    // handle result
    return nil;
}, nil);

Một chuỗi những người khác như vậy cũng được gọi là liên tục.

Xử lý lỗi

Hứa hẹn làm cho nó đặc biệt dễ dàng để xử lý lỗi. Lỗi sẽ được chuyển tiếp từ cha mẹ sang con nếu không có trình xử lý lỗi được xác định trong lời hứa cha mẹ. Lỗi sẽ được chuyển tiếp lên chuỗi cho đến khi một đứa trẻ xử lý nó. Do đó, có chuỗi trên, chúng ta có thể thực hiện xử lý lỗi chỉ bằng cách thêm một chuỗi tiếp tục khác của Wap, xử lý một lỗi tiềm ẩn có thể xảy ra bất cứ đâu ở trên :

asyncTaskA()
.then(^id(id result){
    return asyncTaskB(result);
}, nil)
.then(^id(id result){
    return asyncTaskC(result);
}, nil)
.then(^id(id result){
    return asyncTaskD(result);
}, nil)
.then(^id(id result){
    // handle result
    return nil;
}, nil);
.then(nil, ^id(NSError*error) {
    NSLog(@“”Error: %@“, error);
    return nil;
});

Điều này gần giống với phong cách đồng bộ có lẽ quen thuộc hơn với xử lý ngoại lệ:

try {
    id a = A();
    id b = B(a);
    id c = C(b);
    id d = D(c);
    // handle d
}
catch (NSError* error) {
    NSLog(@“”Error: %@“, error);
}

Lời hứa nói chung có các tính năng hữu ích khác:

Ví dụ, có một tham chiếu đến một lời hứa, thông qua thenmột người có thể "đăng ký" nhiều người xử lý như mong muốn. Trong RXPromise, việc xử lý đăng ký có thể xảy ra bất cứ lúc nào và từ bất kỳ luồng nào vì nó hoàn toàn an toàn cho luồng.

RXPromise có một vài tính năng chức năng hữu ích hơn, không được yêu cầu bởi đặc tả Promise / A +. Một là "hủy bỏ".

Hóa ra, hủy bỏ trên mạng là một tính năng vô giá và quan trọng. Ví dụ: một trang web gọi tham chiếu đến một lời hứa có thể gửi canceltin nhắn đó để cho biết rằng nó không còn quan tâm đến kết quả cuối cùng.

Chỉ cần tưởng tượng một tác vụ không đồng bộ tải hình ảnh từ web và sẽ được hiển thị trong bộ điều khiển xem. Nếu người dùng di chuyển ra khỏi bộ điều khiển chế độ xem hiện tại, nhà phát triển có thể triển khai mã gửi thông báo hủy đến imagePromise , từ đó kích hoạt trình xử lý lỗi được xác định bởi Thao tác yêu cầu HTTP nơi yêu cầu sẽ bị hủy.

Trong RXPromise, một thông báo hủy sẽ chỉ được chuyển từ cha mẹ sang con cái của nó, chứ không phải ngược lại. Đó là, một lời hứa gốc rễ của người Hồi giáo sẽ hủy bỏ tất cả các lời hứa của trẻ em. Nhưng một lời hứa con sẽ chỉ hủy bỏ chi nhánh của Google, nơi nó là cha mẹ. Tin nhắn hủy cũng sẽ được chuyển cho trẻ em nếu một lời hứa đã được giải quyết.

Một tác vụ không đồng bộ có thể tự đăng ký trình xử lý cho lời hứa của chính nó và do đó có thể phát hiện khi ai đó hủy nó. Sau đó, nó có thể sớm ngừng thực hiện một nhiệm vụ có thể kéo dài và tốn kém.

Dưới đây là một vài triển khai khác của Promise trong Objective-C được tìm thấy trên GitHub:

https://github.com/Schoonology/aplus-objc
https://github.com/affablebloke/deferred-objective-c
https://github.com/bww/FutureKit
https://github.com/jkubicek/JKPromises
https://github.com/Strilanc/ObjC-CollapsingFutures
https://github.com/b52/OMPromises
https://github.com/mproberts/objc-promise
https://github.com/klaaspieter/Promise
https: //github.com/jameswomack/Promise
https://github.com/nilfs/promise-objc
https://github.com/mxcl/PromiseKit
https://github.com/apleshkov/promises-aplus
https: // github.com/KptainO/Rebelle

và triển khai của riêng tôi: RXPromise .

Danh sách này có khả năng không đầy đủ!

Khi chọn thư viện thứ ba cho dự án của bạn, vui lòng kiểm tra cẩn thận nếu việc triển khai thư viện tuân theo các điều kiện tiên quyết được liệt kê dưới đây:

  • Một thư viện hứa đáng tin cậy SALL được chủ đề an toàn!

    Đó là tất cả về xử lý không đồng bộ và chúng tôi muốn sử dụng nhiều CPU và thực thi đồng thời trên các luồng khác nhau bất cứ khi nào có thể. Hãy cẩn thận, hầu hết các triển khai không phải là chủ đề an toàn!

  • Trình xử lý SALL được gọi không đồng bộ liên quan đến trang web cuộc gọi! Luôn luôn, và không có vấn đề gì!

    Bất kỳ triển khai tử tế nào cũng phải tuân theo một mẫu rất nghiêm ngặt khi gọi các hàm không đồng bộ. Nhiều người thực hiện có xu hướng "tối ưu hóa" trường hợp, trong đó một trình xử lý sẽ được gọi đồng bộ khi lời hứa đã được giải quyết khi trình xử lý sẽ được đăng ký. Điều này có thể gây ra tất cả các loại vấn đề. Xem Đừng phát hành Zalgo! .

  • Cũng cần có một cơ chế để hủy bỏ một lời hứa.

    Khả năng hủy bỏ một tác vụ không đồng bộ thường trở thành một yêu cầu có mức độ ưu tiên cao trong phân tích yêu cầu. Nếu không, chắc chắn sẽ có một yêu cầu nâng cao từ người dùng một thời gian sau khi ứng dụng được phát hành. Lý do nên rõ ràng: bất kỳ tác vụ nào có thể bị đình trệ hoặc mất quá nhiều thời gian để hoàn thành, sẽ bị người dùng hủy bỏ hoặc hết thời gian chờ. Một thư viện hứa hẹn phong nha nên hỗ trợ hủy bỏ.


1
Điều này nhận được giải thưởng cho câu trả lời dài nhất chưa từng có. Nhưng A cho nỗ lực :-)
Travelling Man

3

Tôi nhận ra đây là một câu hỏi cũ nhưng tôi phải trả lời vì câu trả lời của tôi khác với những câu hỏi khác.

Đối với những người nói đó là vấn đề sở thích cá nhân, tôi phải không đồng ý. Có một lý do hợp lý, tốt để thích cái này hơn cái kia ...

Trong trường hợp hoàn thành, khối của bạn được trao hai đối tượng, một đối tượng đại diện cho thành công trong khi khối kia đại diện cho thất bại ... Vậy bạn sẽ làm gì nếu cả hai đều không? Bạn làm gì nếu cả hai có một giá trị? Đây là những câu hỏi có thể tránh được trong thời gian biên dịch và vì vậy chúng nên được. Bạn tránh những câu hỏi này bằng cách có hai khối riêng biệt.

Có các khối thành công và thất bại riêng biệt làm cho mã của bạn có thể kiểm chứng tĩnh.


Lưu ý rằng mọi thứ thay đổi với Swift. Trong đó, chúng ta có thể thực hiện khái niệm Eitherenum để khối hoàn thành duy nhất được đảm bảo có một đối tượng hoặc một lỗi và phải có chính xác một trong số chúng. Vì vậy, đối với Swift, một khối duy nhất là tốt hơn.


1

Tôi nghi ngờ nó sẽ trở thành sở thích cá nhân ...

Nhưng tôi thích các khối thành công / thất bại riêng biệt. Tôi thích tách biệt logic thành công / thất bại. Nếu bạn đã thành công / thất bại lồng nhau, bạn sẽ kết thúc với thứ gì đó dễ đọc hơn (theo ý kiến ​​của tôi ít nhất).

Là một ví dụ tương đối cực đoan về việc lồng nhau như vậy, đây là một số Ruby hiển thị mẫu này.


1
Tôi đã thấy các chuỗi lồng nhau của cả hai. Tôi nghĩ cả hai đều trông tệ, nhưng đó là ý kiến ​​cá nhân của tôi.
Jeffery Thomas

1
Nhưng làm thế nào khác bạn có thể xâu chuỗi các cuộc gọi async?
Frank Shearar

Tôi không biết người đàn ông tôi không biết. Một phần lý do tôi hỏi là vì tôi không thích bất kỳ mã async nào của mình trông như thế nào.
Jeffery Thomas

Chắc chắn rồi. Cuối cùng, bạn viết mã theo kiểu tiếp tục, điều này không gây ngạc nhiên lắm. (Haskell có ký hiệu của nó cho chính xác lý do này: cho phép bạn viết theo phong cách trực tiếp phô trương.)
Frank Shearar

Bạn có thể quan tâm đến việc triển khai ObjC này: github.com/couchdeveloper/RXPromise
e1985

0

Cảm giác này giống như một bản sao hoàn chỉnh, nhưng tôi không nghĩ có câu trả lời đúng ở đây. Tôi đã đi với khối hoàn thành đơn giản vì việc xử lý lỗi vẫn có thể cần được thực hiện trong điều kiện thành công khi sử dụng khối thành công / thất bại.

Tôi nghĩ rằng mã cuối cùng sẽ trông giống như

[target taskWithCompletion:^(id object, NSError *error) {
    if (error) {
        // Oh noes! report the failure.
    } else if (![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
    } else {
        // W00t! I've got my object
    }
}];

hoặc đơn giản

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    // W00t! I've got my object
}];

Không phải đoạn mã tốt nhất và làm tổ nó trở nên tồi tệ hơn

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    [object objectTaskWithCompletion:^(id object2, NSError *error) {
        if (error || ![object validateObject2:&object2 error:&error]) {
            // Oh noes! report the failure.
            return;
        }

        // W00t! I've got object and object 2
    }];
}];

Tôi nghĩ rằng tôi sẽ đi một lúc.

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.