- Cơ chế mới xử lý mới
async/awaitđã được giới thiệu ởSwift 5.5tại WWDC 2021 tho thấy sự tập trung và ưu tiên trong việc phát triển cơ chếconcurencycủaApple. Mặc dù sẽ mất nhiều thời gian nữa đêasync/awaitcó thể trở nên phổ biến và dùng nhiều trong công việc phát triển app nhưng rõ ràng ta đã thấy sự thu hẹp đáng kể về khoảng cách cũng như thân thiện hơn với cácdevelopertrong việc xử lýconcurency.
1/ Thế nào là async/await ?
-
Chúng ta sẽ mất khá nhiều thời gian nếu đi chi tiết trong việc định nghĩa
async/awaitnhưng để ngắn gọn thì chúng ta có thể hiểu là nó cho phép chúng ta đánh dấufunctionbất đồng bộ trongcodevới từ khóaasyncvà yêu cầ chúng ta từ khóaawaitđể gọi cácfunctionđó. Hệ thống sẽ tự động tổ chức cho chúng ta công việc như đợi chờ thực hiện công việc gì đó khi cácfunctionnày hoàn thành mà không cần nhét vào trongblockđể xử lý như trước. -
Lấy ví dụ như ở
structDocumentLoadercófuncđược đánh dấuasync,funcnày sử dụngAPIURLSessionđể tiến hành xử lý công việccall networkbất đồng bộ với từ khóaawaitđểdownloaddatatừURLđược cho:
structDocumentLoader{var urlSession =URLSession.shared
var decoder =JSONDecoder()funcloadDocument(withID id:Document.ID)asyncthrows->Document{let url =urlForForLoadingDocument(withID: id)let(data,_)=tryawait urlSession.data(from: url)returntry decoder.decode(Document.self, from: data)}...}
- Với từ khóa
asyncđặt trong cácfunctionthì khi gọi cácfuntionbất đồng bộ khác thì ta cần thêm từ khóaawaitđể các tiến trình xử lý trongfunctionđó được xếp thứ tự hoàn thành theo thứ cácawaitđược triển khai mà không cần sợ có cácblockđược xử lý ở thời điểm mà chúng ta không xác định rõ.
2/ Gọi async function từ các synchorous context:
- Một câu hỏi được đặt ra là làm sao để chúng ta có thể gọi
functionđược đánh dấuasynctừ cáccontextmà bản thân nó hoạt không hoạt động bất đồng bộ. Nếu chúng ta muốn sử dụngDocumentLoaderbên trên vớiUIKitthì chúng ta cần đưafunctionloadDocumentvào trong mộtTasknhư sau để tiến hành xử lý bất đồng bộ:
classDocumentViewController:UIViewController{privatelet documentID:Document.IDprivatelet loader:DocumentLoader...privatefuncloadDocument(){Task{do{let document =tryawait loader.loadDocument(withID: documentID)display(document)}catch{display(error)}}}privatefuncdisplay(_ document:Document){...}privatefuncdisplay(_ error:Error){...}}
- Chúng ta đã sử dụng cơ chế mặc định
do / try / catchtrong cơ chếerror handlingđể tiến hành xử lý các công việc bất đồng bộ nhưng chúng ta không cần quan tâm đến nhưng thứ nhưweak selfđể tránh việcretain cycle. Thậm chí chúng ta không cần phải tự xử lý tiến hành cập nhậtUIở trênmain queue.
3/ Cài tiến API bất đồng bộ sẵn có với async/await:
- Chúng ta sẽ cùng tìm hiểu một cách thức khác để cải tiến cơ chế xử lý hoạt động bất đồng bộ bằng cách thêm vào
async/awaitpattern. Chúng ta sẽ cùng triển khai mộtCommentLoaderđể load cáccommentvới việc sử dụngcomplietion handler:
structCommentLoader{...funcloadCommentsForDocument(
withID id:Document.ID,
then handler:@escaping(Result<[Comment],Error>)->Void){...}}
- Nhìn như có vẻ chúng ta sẽ cần thêm
async/awaitnhưng trong trường hợp này thì không nhé. Chúng ta sẽ tiến hành xử lý trongextentioncủaCommentLoadervớifunctionloadCommentsForDocumentsử dụngfunctionmớiwithCheckedThrowingContinuationđể có thểwrapvà gọifunctionsẵn có với từ khóaasyncnhư sau:
extensionCommentLoader{funcloadCommentsForDocument(
withID id:Document.ID)asyncthrows->[Comment]{tryawait withCheckedThrowingContinuation { continuation inloadCommentsForDocument(withID: id){ result inswitch result {case.success(let comments):
continuation.resume(returning: comments)case.failure(let error):
continuation.resume(throwing: error)}}}}}
- Với cách triển khai trên giờ chúng ta có thể dễ dàng gọi
methodloadCommentsForDocumentsử dụng từ khóaawaitnhư khi gọi cácAPIbất đồng bộ khác. Và sau đây là cách chúng taupdateDocumentLoadermột cách tự động với cáccommentđượcfetchvề mỗi khidocumentđược load:
structDocumentLoader{var commentLoader:CommentLoadervar urlSession =URLSession.shared
var decoder =JSONDecoder()funcloadDocument(withID id:Document.ID)asyncthrows->Document{let url =urlForForLoadingDocument(withID: id)let(data,_)=tryawait urlSession.data(from: url)var document =try decoder.decode(Document.self, from: data)
document.comments =tryawait commentLoader.loadCommentsForDocument(
withID: id
)return document
}}
4/ Sử dụng single output của Publisher trong Combine:
- Chúng ta cùng xem xét khả năng xử lý
async/awaitmạnh mẽ củaCombine. Với cơ chế gửi đivaluetrongstreamthì tất cả công việc chúng ta là đợi chờ mộtresultđược trả về từ luồng xử lý củaCombine. Tiếp tục sử dụngtechniquechúng ta đã triển khai trước đó với việv mở rộngprotocolPublisher vớimethodsingleResultsẽ trả về chúng ta giá trị đầu tiên và duy nhất đượcemitbởi Publisher. Chúng ta vẫn sẽ sử dụngclosuređể xử lý cơ chếretaingiải phónginstanceAnyCancellablekhi mà các công việc xử lý dữ liệu đã xong.
extensionPublishers{structMissingOutputError:Error{}}extensionPublisher{funcsingleResult()asyncthrows->Output{var cancellable:AnyCancellable?var didReceiveValue =falsereturntryawait withCheckedThrowingContinuation { continuation in
cancellable =sink(
receiveCompletion:{ completion inswitch completion {case.failure(let error):
continuation.resume(throwing: error)case.finished:if!didReceiveValue {
continuation.resume(
throwing:Publishers.MissingOutputError())}}},
receiveValue:{ value inguard!didReceiveValue else{return}
didReceiveValue =true
cancellable?.cancel()
continuation.resume(returning: value)})}}}
- Đến đây thì chúng ta sẽ dễ nhận ra sự tiện lợi và hiệu quả của việc xử dụng
APICombineđể xử lý cơ chếasync/awaitnhư thế nào so với việc sử dụngclosurenhư cách làm phổ thông:
structCommentLoader{...funcloadCommentsForDocument(
withID id:Document.ID)->AnyPublisher<[Comment],Error>{...}}...let comments =tryawait loader
.loadCommentsForDocument(withID: documentID).singleResult()
Nguồn: viblo.asia
