Hello m.n, t đã quay trở lại rồi đây. Ngày hôm nay, t sẽ tiếp tục đi tiếp series về những điều thú vị của Concurrency trong swift 5.5. Và chủ đề mà t muốn nói đến trong blog ngày hôm nay là Checked Continuation và Unsafe Continuation. Blog lần này sẽ nhanh thôi, vì vậy chúng ta cùng bắt đầu ngay thôi nào ~
À, dừng khoảng chừng 2s, dành cho những bạn chưa theo dõi các bài viết trước của series này, thì mọi người có thể đọc nó ở đây: Part 1- Cơ bản về async và await [Part 2- Concurrency và Async/Await] (// update sau khi part2 dc publish)
Được rồi, đi thôi!!
Ở các phần trước, chúng ta đã biết cách gọi các đoạn code bất đồng bộ trong một đoạn code đồng bộ như nào, nhưng trường hợp ngược lại thì sao? Làm thế nào mà chúng ta có thể chúng ta có thể gọi những đoạn mã đồng bộ ngay trong các xử lý bất đồng bộ?
Để cho dễ hiểu, thì chúng ta cùng đặt ra một bài toán: “Ta có một danh sách link ảnh, làm thế nào để ngay sau khi download dc ảnh xuống, chúng ta sẽ hiển thị nó ra giao diện người dùng”
Trước khi đề cập đến phương pháp giải quyết bài toán trên với Concurrency mới, thì hãy thử nghĩ xem với những gì mà m.n đã biết từ trước đến giờ, mọi người giải quyết bài toán này như nào?
Câu trả lời của t chắc cũng là câu trả lời của mọi người đó là sử dụng DispatchQueue - thứ mà chúng ta dùng nó như cơm bữa. Và tất nhiên\, chúng ta sẽ cần phải dùng cả Completion Handle nữa. T sẽ viết đơn giản các đoạn xử lý như sau:
1, Xử lý donwload ảnh với closure
. Function cần thêm @escaping
để không bị mất đi khi thực hiện bất đồng bộ
func fetchImageOld(from urlString: String, completion:@escaping ((UIImage?) -> Void)) {guard let url = URL(string: urlString) else {return completion(nil)}let urlRequest = URLRequest(url: url)URLSession.shared.dataTask(with: urlRequest) { data, response, error inguard let data = data else {return completion(nil)}let image = UIImage(data: data)return completion(image)}.resume()}
2, Thực hiện gọi func fetchImageOld
ở trên và update ảnh đã down vào list và reload lại tableview, sử dụng DispatchQueue
for url in urlList {fetchImageOld(from: url) { img inDispatchQueue.main.async {self.imageList.append(img)self.tableview.reloadData()}}}
=> Cũng khá đơn giản nhưng lại phải quan tâm khá nhiều thứ:
completion(nil)
với trường hợp lỗi@escaping
DispatchQueue.main.async
để quay lại main thread vì update view phải được thực hiện ở MainVới swift 5.5 chúng ta sẽ có thêm một cách để xử lý bài toán trên với async/await
. Và function download ảnh có thể viết lại ngắn gọn như sau:
func fetchImage(from urlString: String) async -> UIImage? {guard let url = URL(string: urlString) else {return nil}let urlRequest = URLRequest(url: url)let (data, _) = try await URLSession.shared.data(for: urlRequest)return UIImage(data: data)}
Tuy nhiên, có một cái khiến t khá đau đầu ở đây là async func
là một function bất đồng bộ và nó chỉ có thể được gọi đến bởi các đoạn code bất đồng bộ khác. Làm thế nào để chúng ta có thể có thể gọi các đoạn mã đồng bộ ngay trong xử lý bất đồng bộ?. Ở đây là việc update ảnh đã tải xuống vào list và load lại tableview - xử lý này bắt buộc phải xảy ra trên luồng code đồng bộ là Main thread
Rất may là swift 5.5 đã cung cấp cho chúng ta giải pháp bằng cách sử dụng:
withCheckedContinuation()
: Khi method này được gọi, nó sẽ tạm dừng task hiện tại, sau đó chúng ta sẽ thêm những xử lý cần thực hiện đồng bộ trong closure mà function cung cấp.resume(:)
để tiếp tục. Method này có thể trả về một giá trị mà mình mong muốn - cái mà có thể đã được xử lý ở code đồng bộ. Việc gọi resume
là rất quan trọng để có thể tiếp tục xử lý bất đồng bộresume
chỉ có thể gọi dc 1 lần trong withCheckedContinuation()
T sẽ sửa đoạn code trên một chút như sau:
func fetchImage(from urlString: String) async {guard let url = URL(string: urlString) else {return}let urlRequest = URLRequest(url: url)do {let (data, _) = try await URLSession.shared.data(for: urlRequest)let image = UIImage(data: data)let _ = await withCheckedContinuation { c in// thêm 1 giá trị vào imagelist và reload tableviewself.imageList.append(image)self.tableView.reloadData()// gọi resume để quay về xử lý bất đồng bộc.resume(returning: image)}} catch {return}}
Sau đó, mình sẽ chỉ cần triển khai 1 cách đơn giản như sau:
Task {// sử dụng TaskGroup để gọi nhiều request cùng lúc.let _ = await withTaskGroup(of: Void.self, body: { group infor url in urlList {group.addTask {await self.fetchImage(from: url)return}}})}
Đó là toàn bộ xử lý cơ bản mà chúng ta cần để giải quyết bài toán mà chúng ta đặt ra bằng cách sử dụng Checked Continuation và async/await. So với cách truyền thống, có thể thấy nó sự ngắn gọn và rõ ràng hơn. Tuy nhiên, không chỉ có vậy xử lý mới đem lại những điểm khác biệt như:
@escaping
nữa.await
sau khi xử lý bất đồng bộ xong và cho ra kết quả -> Không lo vấn đề crash app.Như đã đề cập ở trên, resume(returning:)
chỉ được gọi 1 lần trong withCheckedContinuation
. Lý do là vì Xcode sẽ đảm bảo code sẽ an toàn hơn với runtime
, đảm bảo tính bảo toàn của các Thread hay các xử lý đồng thời. Tuy nhiên, nếu muốn bỏ qua việc check an toàn và các cảnh bảo của xcode thì có thể sử dụng withUnsafeContinuation
.
Việc sử dụng withUnsafeContinuation
tương tự với việc dùng withCheckedContinuation
và cơ chế hoạt động của unsafe continuation cũng tương tự như như checked continuation với các rules giống nhau. Chỉ khác nó không kiểm tra xem chúng ta có tuân thủ các rules hay không. Điều đó có nghĩa là nếu xảy ra lỗi, nó sẽ không được phát hiện sớm và không nhận dc mô tả rõ ràng về lỗi trong trường hợp bị crash.
Ví dụ điển hình ở đây, và t thì chẳng biết tại sao lại bị crash app 🥺
Tóm lại, unsafe continuation không thêm bất kỳ xử lý nào khác so với checked continuation , nó chỉ xoá tất cả các kiểm tra tính chính xác mà checked continuation đã có.
Qua các ví dụ trên, chắc cũng đủ để m.n hiểu hơn về Checked Continuation và Unsafe Continuation nhưng chắc hẳn không biết nên sử dụng cái nào vì nó cũng tương tự nhau. Theo khuyến khích của đội ngũ team Swift và bản thân m, thì nên sử dụng Checked Continuation , cái gì an toàn thì vẫn hơn chứ nhỉ? 😅
Mọi người có thể đọc để hiểu thêm ở đây:
https://www.donnywals.com/the-difference-between-checked-and-unsafe-continuation-in-swift/ https://developer.apple.com/documentation/swift/withcheckedcontinuation(function:_:) https://developer.apple.com/documentation/swift/unsafecontinuation
Hẹn gặp lại mọi người ở những bài viết tiếp theo. Thân ái và quyết thắng 🇻🇳🇻🇳🇻🇳🇻🇳🇻🇳