- Cơ chế mới xử lý mới
async/await
đã được giới thiệu ởSwift 5.5
tại WWDC 2021 tho thấy sự tập trung và ưu tiên trong việc phát triển cơ chếconcurency
củaApple
. Mặc dù sẽ mất nhiều thời gian nữa đêasync/await
có 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ácdeveloper
trong 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/await
nhưng để ngắn gọn thì chúng ta có thể hiểu là nó cho phép chúng ta đánh dấufunction
bất đồng bộ trongcode
với từ khóaasync
và 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ácfunction
nà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ư ở
struct
DocumentLoader
cófunc
được đánh dấuasync
,func
này sử dụngAPI
URLSession
để tiến hành xử lý công việccall network
bất đồng bộ với từ khóaawait
đểdownload
data
từ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ácfunction
thì khi gọi cácfuntion
bấ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ấuasync
từ cáccontext
mà 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ụngDocumentLoader
bên trên vớiUIKit
thì chúng ta cần đưafunction
loadDocument
vào trong mộtTask
như 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 / catch
trong 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/await
pattern. Chúng ta sẽ cùng triển khai mộtCommentLoader
để load cáccomment
vớ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/await
nhưng trong trường hợp này thì không nhé. Chúng ta sẽ tiến hành xử lý trongextention
củaCommentLoader
vớifunction
loadCommentsForDocument
sử dụngfunction
mớiwithCheckedThrowingContinuation
để có thểwrap
và gọifunction
sẵn có với từ khóaasync
như 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
method
loadCommentsForDocument
sử dụng từ khóaawait
như khi gọi cácAPI
bất đồng bộ khác. Và sau đây là cách chúng taupdate
DocumentLoader
một cách tự động với cáccomment
đượcfetch
về 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/await
mạnh mẽ củaCombine
. Với cơ chế gửi đivalue
trongstream
thì 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ụngtechnique
chúng ta đã triển khai trước đó với việv mở rộngprotocol
Publisher vớimethod
singleResult
sẽ trả về chúng ta giá trị đầu tiên và duy nhất đượcemit
bởi Publisher. Chúng ta vẫn sẽ sử dụngclosure
để xử lý cơ chếretain
giải phónginstance
AnyCancellable
khi 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
API
Combine
để xử lý cơ chếasync/await
như thế nào so với việc sử dụngclosure
như 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