Sử dụng async/await trong Swift.
Bài đăng này đã không được cập nhật trong 4 năm
- 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:
struct DocumentLoader {
    var urlSession = URLSession.shared
    var decoder = JSONDecoder()
    func loadDocument(withID id: Document.ID) async throws -> Document {
        let url = urlForForLoadingDocument(withID: id)
        let (data, _) = try await urlSession.data(from: url)
        return try 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ộ:
class DocumentViewController: UIViewController {
    private let documentID: Document.ID
    private let loader: DocumentLoader
    
    ...
    private func loadDocument() {
        Task {
            do {
                let document = try await loader.loadDocument(withID: documentID)
                display(document)
            } catch {
                display(error)
            }
        }
    }
    private func display(_ document: Document) {
        ...
    }
    private func display(_ 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:
struct CommentLoader {
    ...
    func loadCommentsForDocument(
        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:
extension CommentLoader {
    func loadCommentsForDocument(
        withID id: Document.ID
    ) async throws -> [Comment] {
        try await withCheckedThrowingContinuation { continuation in
            loadCommentsForDocument(withID: id) { result in
                switch 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:
struct DocumentLoader {
    var commentLoader: CommentLoader
    var urlSession = URLSession.shared
    var decoder = JSONDecoder()
    func loadDocument(withID id: Document.ID) async throws -> Document {
        let url = urlForForLoadingDocument(withID: id)
        let (data, _) = try await urlSession.data(from: url)
        var document = try decoder.decode(Document.self, from: data)
        document.comments = try await 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.
extension Publishers {
    struct MissingOutputError: Error {}
}
extension Publisher {
    func singleResult() async throws -> Output {
        var cancellable: AnyCancellable?
        var didReceiveValue = false
        return try await withCheckedThrowingContinuation { continuation in
            cancellable = sink(
                receiveCompletion: { completion in
                    switch completion {
                    case .failure(let error):
                        continuation.resume(throwing: error)
                    case .finished:
                        if !didReceiveValue {
                            continuation.resume(
                                throwing: Publishers.MissingOutputError()
                            )
                        }
                    }
                },
                receiveValue: { value in
                    guard !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:
struct CommentLoader {
    ...
    func loadCommentsForDocument(
        withID id: Document.ID
    ) -> AnyPublisher<[Comment], Error> {
        ...    
    }
}
...
let comments = try await loader
    .loadCommentsForDocument(withID: documentID)
    .singleResult()
All rights reserved
 
  
 