Contents
Chào mừng bạn đến với Fx Studio. Hành trình khám phá của chúng ta trong thế giới New Concurrency của Swift 5.5 vẫn còn rất dài. Lần này, chủ đề là AsyncSequence trong Swift. Cũng là một khái niệm mới được thêm vào cho bài toán bất đồng bộ trong Swift. Nó mang trong mình một tham vọng rất là hơn của Swift, nhằm định hình cả tương lai hệ sinh thái Apple.
Nếu mọi việc đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
Tất nhiên, với 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 tool của bạn.
-
- Swift 5.5
- macOS 12.0.x
- Xcode 13.1
Về mặt kiến thức, bạn cần chuẩn bị kha khá kiến thức. Nhất là về bất đồng bộ. Bạn có thể tham khảo các bài viết về bất đồng bộ trên website này tại link dưới đây:
Về mặt demo, bạn có thể sử dụng Playground để thực thi các ví dụ. Nhưng nếu bạn sử dụng 1 iOS Project thì vẫn tốt hơn. Và chúng ta vẫn dùng console để hiển thị kết quả nhóe.
AsyncSequence là gì?
Khái niệm về AsyncSequence cũng rất đơn giản.
AsyncSequence = Async + Sequence
Có nghĩa là bạn sẽ có một chuỗi (Sequence) giống như bao chuỗi bình thường khác trong Swift. Và bạn vẫn áp dụng được các Higher Order Functions cho nó và các thuộc tính & phương thức đặc trưng của một chuỗi.
Điểm khác biệt ở đây là bạn sẽ dùng chúng vào bất đồng bộ. Bạn sẽ phải sử dụng await để chờ lấy giá trị của các phần tử trong AsyncSequence.
Các Async Sequence sẽ được tạo ra bằng việc conform AsyncSequence protocol. Điều này hữu ích khi bạn muốn xử lý các giá trị theo trình tự của chuỗi, mà giá trị của nó cần phải tính toán hoặc chờ đợi từ một nơi nào đó trả về. Bạn sẽ thấy sự tương đông của các Async Sequence như là các Task trong TaskGroup vậy.
Qua trên, mình đã khái quá sơ lược nhất về Async Sequence cho bạn có một cái nhìn hình dung đầu tiên về nó. Tuy nhiên, để hiểu nó rõ hơn thì chúng ta sẽ đi vào các ví dụ demo nhóe!
Ví dụ
Chúng ta sẽ lấy ví dụ với function sau:
func readData() async { if let url = Bundle.main.url(forResource: "data", withExtension: "txt") { do { for try await line in url.lines { print(line) } } catch { print(error) } } }
Nhiệm vụ của function sẽ là đọc file data.txt
. Nhưng có điểm đặc biệt, là chúng ta sẽ sử dụng thuộc tính url.lines
. Nó chính là một AsyncSequence, với kiểu dữ liệu khai báo AsyncLineSequence<URL.AsyncBytes>. Qua nó, bạn có thể đọc từng dòng của dữ liệu của file từ url
.
- Vì nó là một Sequence, nên chúng ta có thể loop từng phần tử của nó.
- Vì nó là một Async, nên cần dùng await để truy cập giá trị của nó, hay các phần tử của nó
Bạn hãy tưởng tượng file data.txt
này được lưu trử ở server và dữ liệu lấy về thông qua một API. Lúc này, chúng ta sẽ không cần tới những tứ rườm ra như là callback, closure, delegate …
Các AsyncSequence trong SDK Apple
Ngoài ra, Apple cũng thêm các API sử dụng AsyncSequence trong SDK của Swift. Như:
- FileHandle.standardInput.bytes.lines để đọc dữ liệu theo từng dòng
- URL với lines hoặc bytes
- URLSession với phương thức bytes(from:)
- NotificationCenter để chờ nhận các tin nhắn mới, sử dụng kèm theo await (nó sẽ là một kiểu mới nữa nha)
Đó là những API mà bạn sẽ sử dụng nhiều sau này. Và vẫn còn nhiều thứ được cập nhật nữa, nhưng trong phạm vi bài viết thì mình chưa thể giới thiệu hết cho bạn biết.
Higher Order Functions
Một trong những tính năng hay được sử dụng nhất của các chuỗi (Sequence) đó chính là kết hợp với các Higher Order Functions. Và AsyncSequence cũng kế thừa tính năng này nhóe.
Bạn đừng hiểu nhầm nó sang Reactive Programming như là RxSwift hay Combine nhóe. Còn để biết Higher Order Function là gì, thì đọc bài viết này.
Ta sẽ nâng cấp tiếp ví dụ ở trên.
struct MyItem { var number: Int } func readData() async { if let url = Bundle.main.url(forResource: "data", withExtension: "txt") { do { let items = url.lines .map { Int($0) ?? 0 } .filter { $0 % 2 != 0 } .map { MyItem(number: $0) } for try await item in items { print(item.number) } } catch { print(error) } } }
Chúng ta sẽ khai báo thêm một struct MyItem để có được kiểu dữ liệu riêng của chúng ta. Và áp dụng các Higher Order Functions vào chính url.lines
nhóe. Trong đó:
- map lần 1 để biến AsyncBytes thành Int
- filter để loại các phần tử lẻ
- map lần 2 để biến đổi Int thành các MyItem
Cuối cùng, bạn sẽ dùng items
vào trong một vòng for
để duyệt lần lượt các phần tử của nó. Mọi thứ hoạt động một cách nhịp nhàng với nhau. Bạn sẽ không cần lo lắng tới các việc handle, call back … Hãy thực thi function và cảm nhận kết quả nhóe!
AsyncSequence Protocol
Như ví dụ trên, thì url.lines
là một AsyncSequence. Nó được conform với AsyncSequence Protocol. Do đó, bạn có thể tự tạo là một kiểu dữ liệu của riêng bạn, là một AsyncSequence. Chỉ cần đơn giản là conform với AsyncSequence Protocol thôi nhóe.
Khai báo
Chúng ta sẽ tạo mới một kiểu dữ liệu là AsyncSequence nhóe. Bạn xem qua ví dụ khai báo ở dưới đây.
struct Typing: AsyncSequence { typealias Element = String func makeAsyncIterator() -> AsyncIterator { // .... } }
Ví dụ, khi báo struct Typing với conform AsyncSequence. Bạn sẽ cung cấp thêm kiểu dữ liệu chính cho Element. Và cung cấp thêm cho nó một function để thực hiện việc lặp, đó là makeAsyncIterator()
. Và function makeAsyncIterator() sẽ trả về một đối tượng với kiểu AsyncIteratorProtocol.
AsyncIterator Protocol
Bạn cần khai báo thêm một kiểu mới conform AsyncIterator Protocol, để đảm đương nhiệm vụ lặp các phần tử trong Sequence. Công việc sẽ là:
- Conform Protocol nữa là AsyncIteratorProtocol & 1 function để tạo ra đối tượng của Iteractor
- Function next() để tính toán phần tử tiếp theo. Function này sẽ là bất đồng bộ
- Khi return bằng
nil
thì sẽ kết thúc Sequence này
Chúng ta sẽ hoàn thiện struct Typing luôn nhóe!
struct Typing: AsyncSequence { typealias Element = String let phrase: String struct AsyncIterator: AsyncIteratorProtocol { var index: String.Index let phrase: String init(_ phrase: String) { self.phrase = phrase self.index = phrase.startIndex } mutating func next() async throws -> String? { guard index < phrase.endIndex else { return nil } await Task.sleep(1_000_000_000) //nano sec defer { index = phrase.index(after: index) } return String(phrase[phrase.startIndex...index]) } } func makeAsyncIterator() -> AsyncIterator { AsyncIterator(phrase) } }
Nhiệm vụ của Typing sẽ là:
- Nhập 1 câu hay 1 chuỗi String cho
phrase
- Tạo ra một Sequence từ bên ngoài có thể thấy được
- Các phần tử có kiểu dữ liệu là String
- Giá trị các phần tử là các String mới, được tạo theo các
index
tăng dần - Duyệt các phần tử tại
next()
, trong đó chúng ta có 1 hành động là “chờ 1 giây“. Nó là bất đồng bộ - Return
nil
khiindex
đã duyệt hết chuỗi ban đầu và kết thúc cả quá trình
Thực thi chương trình và cảm nhận kết quả nào!
Task { for try await item in Typing(phrase: "Hello, Fx Studio!") { print(item) } }
Kết quả in ra thì như sau:
H He Hel Hell Hello Hello, Hello, Hello, F Hello, Fx Hello, Fx Hello, Fx S Hello, Fx St Hello, Fx Stu Hello, Fx Stud Hello, Fx Studi Hello, Fx Studio Hello, Fx Studio!
Tạm kết
- Giới thiệu khái niệm AsyncSequence và các đặc tính cơ bản của nó
- Các AsyncSequence được cung cấp trong SDK
- Kết hợp với các Higher Order Functions
- Tạo một kiểu AsyncSequence riêng của bạn, với AsyncSequence Protocol
Okay! Tới đây, mình xin kết thúc bài viết giới thiệu về AsyncSequence 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.
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
- 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
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- 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)