API가 하나의 완료 처리기 또는 성공 / 실패 블록 쌍 을 제공하는지 여부 는 주로 개인 취향의 문제입니다.
약간의 차이 만 있지만 두 방법 모두 장단점이 있습니다.
예를 들어, 하나의 완료 핸들러가 최종 결과 또는 잠재적 오류를 결합 하는 하나의 매개 변수 만 가질 수있는 추가 변형도 고려하십시오 .
typedef void (^completion_t)(id result);
- (void) taskWithCompletion:(completion_t)completionHandler;
[self taskWithCompletion:^(id result){
if ([result isKindOfError:[NSError class]) {
NSLog(@"Error: %@", result);
}
else {
...
}
}];
이 서명의 목적은 완료 핸들러 를 다른 API에서 일반적으로 사용할 수 있다는 것 입니다.
예를 들어 Category for NSArray에는 forEachApplyTask:completion:
각 객체에 대해 작업을 순차적으로 호출하고 오류가 발생한 루프 IFF를 중단 시키는 메소드 가 있습니다. 이 메소드 자체도 비동기식이므로 완료 핸들러도 있습니다.
typedef void (^completion_t)(id result);
typedef void (^task_t)(id input, completion_t);
- (void) forEachApplyTask:(task_t)task completion:(completion_t);
실제로 completion_t
위에서 정의한대로 모든 시나리오를 처리하기에 충분하고 충분합니다.
그러나 비동기 작업이 완료 알림을 호출 사이트에 알리는 다른 방법이 있습니다.
약속
"미래", "지연"또는 "지연" 이라고도하는 약속 은 비동기 작업 의 최종 결과를 나타냅니다 (wiki 미래 및 약속 참조 ).
처음에는 약속이 "보류 중"상태입니다. 즉, "값"은 아직 평가되지 않았으며 아직 사용할 수 없습니다.
Objective-C에서 Promise는 다음과 같이 비동기 메소드에서 리턴되는 일반 오브젝트입니다.
- (Promise*) doSomethingAsync;
! 약속의 초기 상태는 "대기 중"입니다.
한편, 비동기 작업은 결과를 평가하기 시작합니다.
또한 완료 핸들러가 없습니다. 대신, 약속은 콜 사이트가 비동기 작업의 결과를 얻을 수있는보다 강력한 수단을 제공 할 것입니다.
promise 객체를 생성 한 비동기 작업은 결국 해당 약속을 "해결"해야합니다. 즉, 작업이 성공 또는 실패 할 수 있기 때문에 평가 된 결과를 전달하는 약속을 "수행"해야하거나 실패 이유를 나타내는 오류를 전달한 약속을 "거부"해야합니다.
! 작업은 결국 약속을 해결해야합니다.
약속이 해결되면 더 이상 값을 포함하여 상태를 변경할 수 없습니다.
! 약속은 한 번만 해결할 수 있습니다 .
약속이 해결되면 콜 사이트는 결과 (실패 또는 성공 여부)를 얻을 수 있습니다. 이것이 달성되는 방법은 약속이 동기식 스타일을 사용하는지 아니면 비동기식 스타일을 사용하여 구현되는지에 따라 다릅니다.
약속 중 하나에있는 리드 동기 또는 비동기 스타일로 구현 될 수있다 블로킹 각각 비 차단 의미.
약속 값을 검색하기 위해 동기식 스타일에서 콜 사이트는 약속이 비동기 작업으로 해결되고 최종 결과가 제공 될 때까지 현재 스레드 를 차단 하는 메소드를 사용 합니다.
비동기 스타일에서 콜 사이트는 약속이 해결 된 후 즉시 호출되는 콜백 또는 핸들러 블록을 등록합니다.
동기식 스타일에는 비동기 작업의 장점을 효과적으로 극복 할 수있는 여러 가지 중요한 단점이 있습니다. 표준 C ++ 11 lib에서 현재 결함이있는 "미래"구현에 대한 흥미로운 기사는 여기에서 읽을 수 있습니다 : Broken promises–C ++ 0x futures .
Objective-C에서 콜 사이트는 어떻게 결과를 얻습니까?
글쎄, 아마도 몇 가지 예를 보여주는 것이 가장 좋습니다. Promise를 구현하는 몇 가지 라이브러리가 있습니다 (아래 링크 참조).
그러나 다음 코드 스 니펫에는 GitHub RXPromise 에서 제공되는 Promise 라이브러리의 특정 구현을 사용합니다 . 저는 RXPromise의 저자입니다.
다른 구현에는 비슷한 API가있을 수 있지만 구문에는 작고 미묘한 차이가있을 수 있습니다. RXPromise는 Promise / A + 사양 의 Objective-C 버전으로 JavaScript에서 강력하고 상호 운용 가능한 약속 구현을위한 공개 표준을 정의합니다.
아래에 나열된 모든 promise 라이브러리는 비동기 스타일을 구현합니다.
서로 다른 구현 간에는 상당한 차이가 있습니다. RXPromise는 디스패치 lib를 내부적으로 사용하고, 스레드 안전하고, 매우 가벼우 며, 취소와 같은 여러 가지 유용한 기능을 제공합니다.
콜 사이트는“등록”핸들러를 통해 비동기 작업의 최종 결과를 얻습니다. "Promise / A + 사양"은 방법을 정의합니다 then
.
방법 then
RXPromise를 사용하면 다음과 같이 보입니다.
promise.then(successHandler, errorHandler);
여기서 successHandler 는 약속이“완료” 되었을 때 호출되는 블록 이고 errorHandler 는 약속이“거절”되었을 때 호출되는 블록입니다.
! then
최종 결과를 얻고 성공 또는 오류 처리기를 정의하는 데 사용됩니다.
RXPromise에서 핸들러 블록의 서명은 다음과 같습니다.
typedef id (^success_handler_t)(id result);
typedef id (^error_handler_t)(NSError* error);
success_handler에는 비동기 작업의 결과 인 명백한 매개 변수 결과 가 있습니다. 마찬가지로 error_handler에는 비동기 작업이 실패했을 때보고 된 오류 인 매개 변수 오류 가 있습니다.
두 블록 모두 반환 값을 갖습니다. 이 반환 값은 곧 명확해질 것입니다.
RXPromise에서, then
A는 특성 블록을 반환합니다. 이 블록에는 성공 처리기 블록과 오류 처리기 블록의 두 매개 변수가 있습니다. 핸들러는 호출 사이트에서 정의해야합니다.
! 핸들러는 호출 사이트에서 정의해야합니다.
따라서 표현 promise.then(success_handler, error_handler);
은
then_block_t block promise.then;
block(success_handler, error_handler);
더 간결한 코드를 작성할 수 있습니다.
doSomethingAsync
.then(^id(id result){
…
return @“OK”;
}, nil);
코드는“doSomethingAsync 를 실행 하면 성공하면 성공 처리기 를 실행합니다”라고 읽습니다 .
여기서 오류 처리기는 nil
오류가 발생하면이 약속에서 처리되지 않습니다.
또 다른 중요한 사실은 속성에서 반환 된 블록을 호출하면 then
약속이 반환된다는 것입니다.
! then(...)
약속을 돌려줍니다
property then
에서 반환 된 블록을 호출하면 "수신자"는 새로운 약속 인 자식 약속을 반환합니다 . 수신자는 부모의 약속이됩니다.
RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);
그게 무슨 뜻이야?
이로 인해 비동기 작업을 "연쇄"하여 효과적으로 순차적으로 실행할 수 있습니다.
또한 각 핸들러의 반환 값은 반환 된 약속의 "값"이됩니다. 따라서 작업이 최종 결과 @“OK”로 성공하면 반환 된 약속은 @“OK”값으로“해결”(“완료”)됩니다.
RXPromise* returnedPromise = asyncA().then(^id(id result){
return @"OK";
}, nil);
...
assert([[returnedPromise get] isEqualToString:@"OK"]);
마찬가지로, 비동기 작업이 실패하면 반환 된 약속이 오류와 함께 해결됩니다 ( "거부 됨").
RXPromise* returnedPromise = asyncA().then(nil, ^id(NSError* error){
return error;
});
...
assert([[returnedPromise get] isKindOfClass:[NSError class]]);
핸들러는 다른 약속을 반환 할 수도 있습니다. 예를 들어 해당 핸들러가 다른 비동기 작업을 실행하는 경우 이 메커니즘을 통해 비동기 작업을 "체인"할 수 있습니다.
RXPromise* returnedPromise = asyncA().then(^id(id result){
return asyncB(result);
}, nil);
! 핸들러 블록의 반환 값은 자식 약속의 값이됩니다.
자식 약속이 없으면 반환 값이 적용되지 않습니다.
더 복잡한 예 :
여기서는 실행 asyncTaskA
, asyncTaskB
, asyncTaskC
및 asyncTaskD
순차 - 각각의 후속 작업이 입력으로 이전 작업의 결과를 취
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);
이러한 "체인"은 "계속"이라고도합니다.
오류 처리
약속을 통해 특히 오류를 쉽게 처리 할 수 있습니다. 부모 약속에 오류 처리기가 정의되어 있지 않으면 부모에서 자식으로 오류가 "전달됩니다". 자식이 처리 할 때까지 오류가 체인으로 전달됩니다. 따라서 위의 체인을 가지면 위의 어느 곳에서나 발생할 수있는 잠재적 오류를 처리하는 다른 "연속"을 추가하여 오류 처리를 구현할 수 있습니다 .
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;
});
이것은 예외 처리와 함께 아마도 더 친숙한 동기 스타일과 비슷합니다.
try {
id a = A();
id b = B(a);
id c = C(b);
id d = D(c);
// handle d
}
catch (NSError* error) {
NSLog(@“”Error: %@“, error);
}
일반적으로 약속에는 다른 유용한 기능이 있습니다.
예를 들어, 약속에 대한 참조가 then
있으면 원하는 수의 처리기를 "등록"할 수 있습니다. RXPromise에서 등록 핸들러는 스레드로부터 완전히 안전하므로 언제 어디서나 스레드에서 등록 할 수 있습니다.
RXPromise에는 Promise / A + 사양에 필요하지 않은 몇 가지 유용한 기능이 있습니다. 하나는 "취소"입니다.
"취소"는 매우 중요하고 중요한 기능입니다. 예를 들어 약속에 대한 참조를 보유한 콜 사이트 cancel
는 더 이상 최종 결과에 관심이 없음을 나타 내기 위해 메시지를 보낼 수 있습니다.
웹에서 이미지를로드하고 뷰 컨트롤러에 표시되는 비동기 작업을 상상해보십시오. 사용자가 현재 뷰 컨트롤러에서 멀어지면 개발자는 취소 메시지를 imagePromise로 보내는 코드를 구현 하여 요청이 취소 될 HTTP 요청 작업에 의해 정의 된 오류 처리기를 트리거합니다.
RXPromise에서 취소 메시지는 부모에서 자식에게만 전달되며 그 반대도 마찬가지입니다. 즉,“뿌리”약속은 모든 어린이 약속을 취소합니다. 그러나 어린이 약속은 부모 인“지점”만 취소합니다. 약속이 이미 해결 된 경우 취소 메시지도 어린이에게 전달됩니다.
비동기 작업은 자체 약속을 위해 처리기를 자체 등록 할 수 있으므로 다른 사람이이를 취소 한시기를 감지 할 수 있습니다. 그러면 시간이 오래 걸리고 비용이 많이 드는 작업 수행이 조기에 중단 될 수 있습니다.
GitHub에서 찾은 Objective-C의 Promises 구현은 다음과 같습니다.
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
내 자신의 구현 : RXPromise .
이 목록은 완전하지 않을 수 있습니다!
프로젝트의 세 번째 라이브러리를 선택할 때 라이브러리 구현이 아래 나열된 전제 조건을 따르는 지주의 깊게 확인하십시오.
신뢰할 수있는 약속 라이브러리는 스레드 안전해야합니다!
비동기 처리에 관한 것이므로 여러 CPU를 활용하고 가능할 때마다 다른 스레드에서 동시에 실행하려고합니다. 대부분의 구현은 스레드로부터 안전하지 않습니다.
콜 사이트와 관련하여 핸들러를 비동기식 으로 호출해야합니다! 항상, 그리고 무엇이든!
적절한 구현은 비동기 함수를 호출 할 때 매우 엄격한 패턴을 따라야합니다. 많은 구현 자는 처리기가 등록 될 때 약속이 이미 해결 될 때 처리기가 동 기적 으로 호출되는 경우를 "최적화"하는 경향이 있습니다 . 이로 인해 모든 종류의 문제가 발생할 수 있습니다. Zalgo를 놓지 마십시오를 참조하십시오 ! .
약속을 취소하는 메커니즘도 있어야합니다.
비동기 작업을 취소 할 수있는 가능성은 종종 요구 사항 분석에서 우선 순위가 높은 요구 사항이됩니다. 그렇지 않은 경우 앱이 출시 된 후 얼마 후 사용자로부터 개선 요청이 접수됩니다. 그 이유는 분명해야합니다. 중지되거나 완료하는 데 너무 오래 걸리는 작업은 사용자 또는 시간 초과로 취소 할 수 있어야합니다. 괜찮은 약속 라이브러리는 취소를 지원해야합니다.