Contents
Chào mừng bạn đến với Fx Studio. Bài viết lần này sẽ mang tính chất tổng hợp và giới thiệu các khái niệm mới của New Concurrency trong Swift 5.5 tới bạn. Cũng như cách tiếp cận theo từng bước để bước vào thế giới huyền bí này.
Nếu mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
Tất nhiên, với các khái niệm mới này thì bạn cần chuẩn bị Swift của mình là mới nhất. Sau đây là version cho các tools của bạn.
-
- Swift 5.5
- Xcode 13.x
Về mặt kiến thức, Concurrency chưa bao giờ là đơn giản đối với bất kỳ dev iOS ở mọi level nào cả. Do đó, bạn cần phải nắm rõ các kiến thức cơ bản về Swift & iOS trước. Ngoài ra, các kiến thức sau cũng cầm bạn có độ am hiểu nhất định.
Đó là những kiến thức mà mình nghĩ là sẽ giúp bạn rất nhiều khi bắt đầu tìm hiểu về Concurrency trong Swift. Áp dụng cho cả phương pháp cũ và mới của Concurrency. Ngoài ra, phạm vi bài viết chỉ nói về các kiến thức & khái niệm mới có trong Swift 5.5. Nhưng bên cạnh đó, chúng ta cũng có rất nhiều vấn đề liên quan tới Concurrency nói chung và bạn có thể đọc thêm các bài viết tại link dưới đây.
Concurrency Roadmap
Từ WWDC21, Apple đã giới thiệu Swift 5.5 với rất nhiều cập nhật cho Xử lý bất đồng bộ & Đồng thời. Tới hiện tại, Apple vẫn tiếp tục cập nhất thêm các API mới của New Concurrency này. Các APIs này ra đời với mục đích giúp Swift tiếp cận với các ngôn ngữ lập trình mới trong xử lý đồng thời.
Quan trọng hơn nữa là thay đổi cách tư duy lập trình của bạn về Concurrency trong Swift. Vì trước đây, lý do hầu hết sẽ là …
Concurrency là khó!
Nhưng với các khái niệm mới (như là: async/await, Task …) sẽ làm logic code của bạn thu gọn đi rất nhiều. Tính liền mạch trong code đảm bảo. Bạn không cần phải tưởng tượng hoặc suy nghĩ các tiến trình chạy/nhảy như thế này. Chúng ta sẽ chia tay một số khái niệm kinh điển khi xử lý bất đồng bộ & đồng thời trước đây là:
Callback & Delegate
Từ đó, các bạn newbie khi học Swift sẽ đỡ phải áp lực hơn trước đây. Nhưng đó là về mặt tiếp cận, còn về bản chất thì bạn phải nắm được các kiến thức cơ bản trước. Và New Concurrency này cũng là một phần nâng cao của Swift.
Cuối cùng, mục đích quan trọng nhất của New Concurrency đó là tích hợp vào hệ sinh thái của Apple. Nó sẽ hỗ trợ trực tiếp cho cách nền tảng mới, như SwiftUI. Tằng cường thêm sức mạnh của Combine và xóa bỏ dần lệ thuộc với Rx …
Và vấn đề lớn nhất lúc này là bạn sẽ tiếp cận chúng bắt đầu từ đâu?
Bài viết này sẽ hướng dẫn bạn cách tìm hiểu như thế nào để bạn không phải tẩu hỏa nhập ma. Nào tiếp tục thôi!
async & await
Về async & await là 2 keyword
của hệ thống. Chúng là 2 keyword sẽ đi theo bạn rất nhiều. Để bắt đầu tìm hiểu về Concurrency mới này, thì bạn sẽ phải hiểu được cách sử dụng của async & await trong Swift 5.5 nhóe.
Vấn đề
Chúng ta đã được học lập trình với kiểu lập trình tuyến tính, nghĩa là code của bạn sẽ chạy từ trên xuống dưới và theo các cấu trúc nhất định. Nhưng khi chuyển sang bất đồng bộ, thì bạn phải suy nghĩ rằng một số đoạn code của bạn sẽ được thực thi tại một nơi nào đó hoặc một thời điểm nào đó.
Bạn sẽ nhận được giá trị của tác vụ trả về thông qua callback & delegate. Đó cũng chính là thiết kế cơ bản của Objective-C & Swift từ lúc khai thiên lập địa tới bây giờ.
Điều đó dẫn tới việc hiểu được bất đồng bộ & đồng thời rất là khó.
Giải pháp
async & await cho phép chúng ta viết mã đồng thời tuyến tính thực thi từ trên xuống dưới. Để làm việc với điều này, các hàm có thể được gọi là bất đồng bộ nên được đánh dấu là bất đồng bộ trong khai báo hàm. Chà, xem ví dụ là hiểu liền.
func doSomething() async -> String { return "n/a" }
Để khai báo một làm là bất đồng bộ, bạn cần thêm từ khóa async
vào khai báo. Khi chúng ta gọi/thực thi một hàm được đánh dấu là bất đồng bộ, nó cần được thêm vào trước từ await
.
func call() async { let str = await doSomething() print(str) }
Khi quá trình thực thi code của chúng ta đến từ khóa await, việc thực thi code của chúng ta có thể bị tạm ngừng và các đoạn code khác vẫn thực thi công việc của nó.
Đó là những gì cơ bản nhất của async & await trong Swift 5.5. Tất nhiên vẫn còn nhiều vấn đề liên quan tới chúng nữa, bạn sẽ tìm hiểu ở bài viết dưới đây.
Ứng dụng
Để giúp bạn thấy được ứng dụng của async & await thì đọc tiếp bài viết dưới đây:
Lần này, chúng ta sẽ thử sử dụng nó vào việc lấy dữ liệu từ một Rest API. Bên cạnh đó, ta cũng sẽ phân tích xem cách dùng mới và cũ có gì khác nhau. Hy vọng bạn sẽ bắt đầu hứng thú với async & await mới này.
Quan trong hơn, bạn sẽ thấy việc chúng ta sẽ nói lời chia tay với Callback trong xử lý tương tác với API.
Structured Concurrency
Khái niệm cần tìm hiểu tiếp theo là Structured Concurrency. Vì với async & await là dừng chờ, chứ không phải là Đồng thời. Độ khó công việc của bạn cần giải quyết là xử lý một lúc nhiều tác vụ bất đồng bộ với nhau.
Structured Concurrency cho phép chúng ta viết code đồng thời cũng có thể được đọc từ trên xuống dưới. Chúng ta có thể khởi chạy song song nhiều tác vụ một cách dễ dàng. Và bạn sẽ có 2 kiểu mà Structured Concurrency cung cấp cho bạn, đó là:
-
- async let
- Task Group
async let
Bạn cần biết các tác vụ gọi với await, thì chúng cũng có thể thực hiện đồng thời. Chúng ta sẽ tưởng tượng các tác vụ đó như là một giá trị và việc cần làm lúc này định nghĩa chúng thành một biến.
Từ đó, ta sẽ kết hợp thêm cho việc khai báo đó là:
async + let
Và bỏ đi từ khóa await. Chúng sẽ như thế này:
async let thing = doSomething() makeUseOf(await thing)
Lúc nào bạn sử dụng tới giá trị của chúng, thì sẽ sử dụng từ khóa await. Đơn giản như vậy thôi! Còn khi bạn kết hợp nhiều async let lại với nhau, thì sẽ thấy được tính năng đồng thời của chúng. Xem ví dụ nhóe!
func downloadImageAndMetadata(imageNumber: Int) async throws -> DetailedImage { async let image = downloadImage(imageNumber: imageNumber) async let metadata = downloadMetadata(for: imageNumber) return try DetailedImage(image: await image, metadata: await metadata) }
Trong đó:
- Khai báo 2 async let cho
image
vàmetadata
thì chúng không cần phải dừng chờ lẫn nhau, mà sẽ được thực thi ngay - Khi muốn lấy giá trị của chúng thì bạn sử dụng await
- Toàn bộ tiến trình sẽ kết thúc khi cả 2 async let đó kết thúc hết
Để biết thêm về chúng thì bạn đọc thêm bài viết này:
Group Task
Tiếp theo, bạn có nhiều tác vụ trả về kiểu dữ liệu giống nhau. Thì các tốt nhất là nhóm chúng nó lại thành một nhóm để dễ quản lý. Từ đó, Group Task được ra đời.
Chúng ta sẽ sử dụng 2 phương thức withThrowingTaskGroup hoặc withTaskGroup, để tạo ra các Group Task. Với mỗi async let được xem như là một Task con, hoặc các function async cũng xem là một Task con. Bạn sử dụng group.addTask { … } để thêm chúng vào.
func printMessage() async { let string = await withTaskGroup(of: String.self) { group -> String in group.addTask { "Hello" } group.addTask { "From" } group.addTask { "A" } group.addTask { "Task" } group.addTask { "Group" } var collected = [String]() for await value in group { collected.append(value) } return collected.joined(separator: " ") } print(string) } // thực thi Task { await printMessage() }
Ưu điểm của Group Task sẽ giúp bạn quản lý các task con tốt hơn. Chủ động cancel chúng hoặc xử lý giá trị trả về của các task con … Để biết thêm về chúng thì bạn đọc thêm bài viết này:
Sendable Types
Để phục vụ cho các function và API của Concurrency, chúng ta cần phải có kiểu dữ liệu phù hợp với chúng. Đó là Sendable Type.
Sendable Type là kiểu dữ liệu mà có thể được chia sẽ một cách an toàn trong Concurrency.
Apple định nghĩa chúng với Sendable Protocol và được tích hợp sẵn vào các kiểu dữ liệu cơ bản (như: Int, String, Float …). Còn với các kiểu dữ liệu của riêng bạn, thì cần thuân thủ Sendable Protocol khi khai báo.
final class User: Sendable { let name: String init(name: String) { self.name = name } }
Cuối cùng là @Sendable closure, sẽ sử dụng trong khai báo các function với các tham số là closure. Và cũng để đảm bảo các function & closure được an toàn trong các thread đồng thời.
Để biết thêm về chúng thì bạn đọc thêm bài viết này:
Unstructured Concurrency
Đây là cách bạn tiếp cận với xử lý đồng thời linh hoạt nhất. Khi bạn hi sinh logic và cấu trúc các tiến trình bất đồng bộ để lấy sự đơn giản. Bạn có thể áp dụng nó ở bất cứ ngữ cảnh nào trong project của bạn, mà không thay đổi tới cấu trúc code hiện tại.
Ngoài ra, Unstructured Concurrency vẫn giúp bạn kiểm soát được các tác vụ của mình. Có thể lưu trữ, lấy giá trị và hủy bỏ. Chúng ta có 2 cách tiếp cận tới Unstructured Concurrency như sau:
Task
Khi bạn sử dụng Task {}, bạn thực sự đang khởi chạy một tác vụ đồng thời. Đây là cách thực hiện cầu nối giữa thế giới bất đồng bộ và đồng bộ.
Task { print("hello") }
Bạn có thể lưu trữ chúng trong các biến để có thể hủy chúng theo cách thủ công khi cần thiết. Chúng ta có thể khai báo một thuộc tính với kiểu dữ liệu là Task<T, Error>.
var downloadImageTask: Task<Void, Never>? { didSet { if downloadImageTask == nil { tapButton.setTitle("Download", for: .normal) } else { tapButton.setTitle("Cancel", for: .normal) } } }
Để biết thêm về chúng thì bạn đọc thêm bài viết này:
Detached Task
Cách bạn tạo ra một task mới không thuộc các task đang chạy. Nó hoàn toàn độc lập và không chịu sự ảnh hưởng của các task cha nó. Bằng cách sử dụng:
Task.detached { print("hello 2") }
Không giống như các loại tác vụ khác, chúng không kế thừa bất kỳ thứ gì từ tác vụ mẹ của chúng. Thậm chí không phải là ưu tiên. Chúng độc lập với bối cảnh mà chúng được khởi chạy.
Để biết thêm về chúng thì bạn đọc thêm bài viết này:
Actors
Vấn đề sẽ phát sinh với dữ liệu khi xử lý bất đồng bộ & đồng thời, đó là Data Race. Khái niệm mới cho một kiểu dữ liệu mới được ra đời nhằm giải quyết vấn đề trên.
Actor là một dữ liệu tham chiếu (reference type) mà bảo vệ việc truy cập vào các trạng thái có thể thay đổi được của nó. Trạng thái của tác nhân (Actor) chỉ được truy cập bởi một luồng duy nhất tại bất cứ thời điểm nào. Giúp loại bỏ đi nhiều lỗi nghiêm trọng ngay ở level compiler.
Tóm tắt lại các đặc điểm của một Actor đó là:
- Là một kiểu dữ liệu tham chiếu, tương tự như class
- Các thuộc tính của nó sẽ được đảm bảo an toàn
- Chỉ cho phép mỗi thời điểm chỉ một thread có thể truy cập được.
actor MyNumber { var value: Int init(value: Int) { self.value = value } func show() { print(value) } }
Làm việc với Actor thì bạn cần sẽ phải tìm hiểu thêm các khái niệm isolate và các tương tác với Actor nữa. Để tìm hiểu nhiều hơn thì đọc thêm bài viết này:
@MainActor and Global Actors
Đây là sự nâng cấp của Actor, khi các đối tượng chính sẽ được sử dụng ở Main Thread. Mục đích duy nhất là giải quyết các bài toán liên quan tới giao diện ứng dụng.
@MainActor class ViewController: UIViewController { @MainActor var name: String = "Fx Studio" //.... }
Chúng ta sẽ đọc qua 2 bài viết để giải quyết 2 vấn đề cốt lõi ở Main Thread:
Sharing Data with @TaskLocal
Về định nghĩa, TaskLocal Property Wrapper hay @TaskLocal là một Property Wrapper. Với giá trị của TaskLocal, thì có thể đọc & ghi được từ ngữ cảnh của một Task. Nó được hiểu chia sẽ ngầm định và truy cập được từ bất kỳ Task con nào mà Task cha đó tạo ra.
class ViewController: UIViewController { @TaskLocal static var currentName: String? //.... }
Property wrapper @TaskLocal có thể được sử dụng để chia sẻ dữ liệu trong tác vụ cục bộ. Quản lý được việc chia sẽ dữ liệu cho các Task con trong cùng một Task Tree. Cô lập dữ liệu chia sẽ khi xử lý bất đồng bộ & đồng thời tại nhiều Task Tree.
Để biết thêm về chúng thì bạn đọc thêm bài viết này:
Tạm kết
- Tìm hiểu các khái niệm Concurrency mới trong Swift 5.5
- Lộ trình tìm hiểu chúng theo từng bước
- Khái niệm, cách hoạt động & ứng dụng của từng khái niệm mới
Để giúp bạn tiện theo dõi hơn, sau đây là danh sách các bài viết liên quan tới từng phần trình bày ở trên. Bạn có thể đọc và khám phá thêm chi tiết & ứng dụng của chúng.
- Cơ bản về async/await trong 10 phút
- async/await to Fetch REST API
- Structured Concurrency & async let
- Task & Task Group trong 10 phút
- Sendable Protocol & @Sendable trong 10 phút
- Unstructured Concurrency
- Detached Tasks
- Cơ bản về Actor trong 10 phút
- MainActor và điều gì xảy ra với UI trên Main Thread
- MainActor và điều gì xảy ra với Data trên Main Thread
- TaskLocal Property Wrapper
Okay! Tới đây, mình xin kết thúc bài viết giới thiệu về Concurrency Roadmap trong Swift 5.5 . Nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.
- Bạn có thể checkout code tại đây.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Prompt Engineering trong 10 phút
- Một số ví dụ sử dụng Prompt cơ bản khi làm việc với AI
- Prompt trong 10 phút
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
You may also like:
Archives
- December 2024 (3)
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)