ฉันจะบอกว่าไม่ว่า 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 อื่น ๆ
ตัวอย่างเช่นในหมวดหมู่สำหรับ 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 Futures and contract )
ในขั้นต้นสัญญาอยู่ในสถานะ "รอ" นั่นคือ“ มูลค่า” ยังไม่ได้รับการประเมินและยังไม่พร้อมใช้งาน
ใน Objective-C สัญญาจะเป็นวัตถุธรรมดาซึ่งจะถูกส่งคืนจากวิธีอะซิงโครนัสตามที่แสดงด้านล่าง:
- (Promise*) doSomethingAsync;
! สถานะเริ่มต้นของสัญญาคือ“ รอดำเนินการ”
ในขณะเดียวกันภารกิจอะซิงโครนัสก็เริ่มประเมินผลลัพธ์
โปรดทราบว่าไม่มีตัวจัดการความสมบูรณ์ สัญญาจะให้วิธีที่มีประสิทธิภาพมากขึ้นซึ่งไซต์การโทรสามารถรับผลลัพธ์ในที่สุดของงานอะซิงโครนัสซึ่งเราจะเห็นในไม่ช้า
งานอะซิงโครนัสซึ่งสร้างวัตถุสัญญาต้องในที่สุด "แก้ไข" สัญญา ซึ่งหมายความว่าเนื่องจากงานอาจประสบความสำเร็จหรือล้มเหลวก็ต้อง“ ปฏิบัติตาม” สัญญาที่ส่งผ่านผลลัพธ์ที่ประเมินหรือต้อง“ ปฏิเสธ” สัญญาที่ส่งผ่านข้อผิดพลาดที่ระบุถึงสาเหตุของความล้มเหลว
! ในที่สุดงานก็ต้องแก้ไขสัญญา
เมื่อสัญญาได้รับการแก้ไขแล้วจะไม่สามารถเปลี่ยนแปลงสถานะได้อีกต่อไปรวมถึงมูลค่า
! สัญญาจะสามารถแก้ไขได้เพียงครั้งเดียว
เมื่อสัญญาได้รับการแก้ไขแล้วไซต์การโทรสามารถรับผลลัพธ์ได้ (ไม่ว่าจะล้มเหลวหรือประสบความสำเร็จ) การทำสิ่งนี้สำเร็จได้อย่างไรขึ้นอยู่กับว่าสัญญานั้นถูกนำไปใช้โดยใช้ซิงโครนัสหรือสไตล์อะซิงโครนัส
สัญญาสามารถนำไปใช้ในรูปแบบซิงโครนัสหรือแบบอะซิงโครนัสซึ่งนำไปสู่การปิดกั้นความหมายที่ไม่ปิดกั้นตามลำดับ
ในรูปแบบซิงโครนัสเพื่อดึงค่าของสัญญาไซต์การโทรจะใช้วิธีการที่จะบล็อกเธรดปัจจุบันจนกว่าหลังจากที่สัญญาได้รับการแก้ไขโดยงานแบบอะซิงโครนัสและผลลัพธ์สุดท้ายจะพร้อมใช้งาน
ในลักษณะอะซิงโครนัสไซต์การโทรจะลงทะเบียน callbacks หรือตัวจัดการบล็อกที่ถูกเรียกทันทีหลังจากสัญญาได้รับการแก้ไข
ปรากฎว่าสไตล์ซิงโครนัสมีข้อเสียที่สำคัญหลายประการซึ่งเอาชนะข้อดีของงานอะซิงโครนัสได้อย่างมีประสิทธิภาพ บทความที่น่าสนใจเกี่ยวกับการดำเนินงานในขณะนี้มีข้อบกพร่องของ“ฟิวเจอร์ส” ในมาตรฐาน C ++ 11 lib สามารถอ่านได้ที่นี่: Broken สัญญา-C ++ 0x ฟิวเจอร์ส
ใน Objective-C ไซต์ที่เรียกจะได้รับผลอย่างไร
มันเป็นการดีที่สุดที่จะแสดงตัวอย่าง มีห้องสมุดสองแห่งที่ใช้สัญญา (ดูลิงค์ด้านล่าง)
แต่สำหรับตัวอย่างโค้ดต่อไปผมจะใช้การดำเนินการโดยเฉพาะอย่างยิ่งของห้องสมุดสัญญาที่มีอยู่บน GitHub RXPromise ฉันเป็นผู้เขียน RXPromise
การใช้งานอื่น ๆ อาจมี API ที่คล้ายกัน แต่อาจมีความแตกต่างเล็กน้อยในด้านไวยากรณ์ RXPromise เป็นรุ่น Objective-C ของข้อกำหนดPromise / A +ซึ่งกำหนดมาตรฐานแบบเปิดสำหรับการใช้งานที่มีประสิทธิภาพและใช้งานร่วมกันได้ของสัญญาใน JavaScript
ไลบรารีสัญญาทั้งหมดที่แสดงด้านล่างใช้สไตล์อะซิงโครนัส
มีความแตกต่างอย่างมีนัยสำคัญระหว่างการใช้งานที่แตกต่างกัน RXPromise ใช้ lib การจัดส่งภายในซึ่งปลอดภัยต่อเธรดอย่างเต็มที่มีน้ำหนักเบามากและยังมีคุณสมบัติที่มีประโยชน์เพิ่มเติมมากมายเช่นการยกเลิก
ไซต์การเรียกรับผลลัพธ์ในที่สุดของงานอะซิงโครนัสผ่านตัวจัดการ“ การลงทะเบียน” “การสัญญา / 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
เป็นคุณสมบัติที่ส่งคืนบล็อก บล็อกนี้มีสองพารามิเตอร์บล็อกตัวจัดการความสำเร็จและบล็อกตัวจัดการข้อผิดพลาด ตัวจัดการต้องถูกกำหนดโดยไซต์การโทร
! ตัวจัดการต้องถูกกำหนดโดยไซต์การโทร
ดังนั้นการแสดงออก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);
รหัสอ่าน:“ Execute doSomethingAsync, เมื่อสำเร็จ, จากนั้นจึงใช้โปรแกรมจัดการความสำเร็จ”
ที่นี่ตัวจัดการข้อผิดพลาดnil
หมายถึงในกรณีที่มีข้อผิดพลาดจะไม่ได้รับการจัดการในสัญญานี้
ข้อเท็จจริงสำคัญอีกประการหนึ่งคือการเรียกการบล็อกที่ส่งคืนจากคุณสมบัติthen
จะส่งคืนสัญญา:
! then(...)
ส่งคืนสัญญา
เมื่อโทรบล็อกกลับมาจากสถานที่ให้บริการthen
ที่“รับ” ผลตอบแทนใหม่สัญญาเป็นเด็กสัญญา ผู้รับจะกลายเป็นผู้ปกครองสัญญา
RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);
นั่นหมายความว่าอย่างไร?
ด้วยเหตุนี้เราจึงสามารถ“ เชื่อมโยง” งานอะซิงโครนัสซึ่งดำเนินการตามลำดับได้อย่างมีประสิทธิภาพ
นอกจากนี้ค่าส่งคืนของตัวจัดการทั้งสองจะกลายเป็น "มูลค่า" ของสัญญาที่ส่งคืน ดังนั้นหากงานประสบความสำเร็จกับผลลัพธ์ในที่สุด @“ ตกลง” สัญญาที่ส่งคืนจะ“ แก้ไข” (นั่นคือ“ เติมเต็ม”) ด้วยค่า @“ ตกลง”:
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
ตามลำดับ - และแต่ละงานที่ตามมาจะใช้เวลาของผลงานก่อนหน้านี้เป็น input ไปนี้:
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 มีคุณสมบัติการใช้งานที่มีประโยชน์มากกว่าสองประการโดยไม่จำเป็นต้องใช้ข้อกำหนดของสัญญา / A + หนึ่งคือ "ยกเลิก"
ปรากฎว่า "การยกเลิก" เป็นคุณสมบัติที่ทรงคุณค่าและมีความสำคัญ ตัวอย่างเช่นไซต์โทรที่มีการอ้างอิงถึงสัญญาสามารถส่งcancel
ข้อความเพื่อระบุว่าไม่สนใจผลลัพธ์ในที่สุด
แค่คิดว่างานแบบอะซิงโครนัสซึ่งโหลดรูปภาพจากเว็บและสิ่งที่จะแสดงในตัวควบคุมมุมมอง หากผู้ใช้ย้ายออกจากตัวควบคุมมุมมองปัจจุบันผู้พัฒนาอาจใช้โค้ดที่ส่งข้อความยกเลิกไปยังimagePromiseซึ่งจะทริกเกอร์ตัวจัดการข้อผิดพลาดที่กำหนดโดยการดำเนินการร้องขอ HTTP ซึ่งการร้องขอจะถูกยกเลิก
ใน RXPromise ข้อความยกเลิกจะถูกส่งต่อจากผู้ปกครองไปยังลูก ๆ ของมันเท่านั้น แต่จะไม่ส่งต่อในทางกลับกัน นั่นคือสัญญา“ รูท” จะยกเลิกสัญญาเด็กทั้งหมด แต่คำสัญญาของเด็กจะยกเลิกเฉพาะ "สาขา" ที่เป็นผู้ปกครอง ข้อความยกเลิกจะถูกส่งต่อไปยังเด็ก ๆ หากสัญญาได้รับการแก้ไขแล้ว
เป็นงานที่ไม่ตรงกันสามารถตัวเองลงทะเบียนจัดการสำหรับสัญญาของตัวเองและทำให้สามารถตรวจสอบได้เมื่อมีคนอื่นยกเลิก จากนั้นอาจหยุดทำงานก่อนกำหนดเป็นเวลานานและมีค่าใช้จ่ายสูง
นี่คือการใช้งานอื่น ๆ อีกสองอย่างของสัญญาใน Objective-C ที่พบใน GitHub:
https://github.com/Schoonology/aplus-objc
https://github.com/affablebloke/deferred-objective-c
https://github.com/bww/FutureKit
https://github.com/jkubicek/JKubpromises
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
รายการนี้มีแนวโน้มไม่สมบูรณ์!
เมื่อเลือกห้องสมุดที่สามสำหรับโครงการของคุณโปรดตรวจสอบอย่างรอบคอบว่าการใช้งานห้องสมุดเป็นไปตามข้อกำหนดเบื้องต้นที่ระบุไว้ด้านล่าง:
ห้องสมุดสัญญาที่เชื่อถือได้จะต้องปลอดภัย!
มันคือทั้งหมดที่เกี่ยวกับการประมวลผลแบบอะซิงโครนัสและเราต้องการใช้ซีพียูหลายตัวและดำเนินการกับเธรดที่ต่างกันพร้อมกันทุกครั้งที่ทำได้ ระวังการใช้งานส่วนใหญ่จะไม่ปลอดภัย!
ตัวจัดการจะเรียกว่าแบบอะซิงโครนัสซึ่งเกี่ยวข้องกับไซต์การโทร! เสมอและไม่ว่าอะไรจะเกิดขึ้น!
การใช้งานที่เหมาะสมควรทำตามรูปแบบที่เข้มงวดมากเมื่อเรียกใช้ฟังก์ชันอะซิงโครนัส ผู้ดำเนินการหลายคนมักจะ "เพิ่มประสิทธิภาพ" เคสซึ่งตัวจัดการจะถูกเรียกใช้พร้อมกันเมื่อสัญญาได้รับการแก้ไขแล้วเมื่อตัวจัดการจะลงทะเบียน สิ่งนี้สามารถทำให้เกิดปัญหาได้ทุกประเภท ดูอย่าปล่อย Zalgo! .
ควรมีกลไกในการยกเลิกสัญญา
ความเป็นไปได้ที่จะยกเลิกงานแบบอะซิงโครนัสมักจะกลายเป็นข้อกำหนดที่มีลำดับความสำคัญสูงในการวิเคราะห์ความต้องการ ถ้าไม่เช่นนั้นจะมีการยื่นคำขอการปรับปรุงจากผู้ใช้ในภายหลังหลังจากที่ปล่อยแอพ เหตุผลควรชัดเจน: งานใด ๆ ที่อาจหยุดหรือใช้เวลานานเกินไปที่จะเสร็จสิ้นควรถูกยกเลิกโดยผู้ใช้หรือหมดเวลา ห้องสมุดสัญญาที่ดีควรสนับสนุนการยกเลิก