Lập Trình Bất Đồng Bộ trong Swift

Trong bài viết này, tôi sẽ sử dụng các thư viện bên thứ ba sau để hoàn thành dự án:
- Alamofire: Một framework HTTP networking trong Swift.
- SwiftyJSON: Để xử lý dữ liệu JSON.
- SwiftGifOrigin: Một extension UIImage để hiển thị file Gif.
- Bolts-Swift: Được thiết kế bởi Parse và Facebook, tôi sử dụng nó để tạo các phương thức bất đồng bộ.
- PromiseKit: Một framework giúp chúng ta đơn giản hóa lập trình bất đồng bộ.
- API của Giphy để tìm kiếm và tải ảnh gif.
Bắt đầu
Các phương thức bất đồng bộ, (viết tắt là Async), là các phương thức không trả về kết quả ngay lập tức như hầu hết các phương thức, các phương thức async mất một thời gian để tạo ra kết quả.
Tôi thường sử dụng callback để xử lý các phương thức bất đồng bộ như quét thiết bị Bluetooth hoặc lấy một số tài nguyên từ internet. Thực tế, callback là một kỹ thuật lập trình không tốt. Callback sẽ làm code của chúng ta khó đọc, khó debug và tốn nhiều thời gian hơn để bảo trì sau này. Cuối cùng, code của chúng ta sẽ biến thành thứ mà chúng ta gọi là callback hell.
Trong bài viết này, tôi sẽ tạo một dự án sử dụng từng kỹ thuật một để giải thích tại sao tôi nói callback là không tốt.
Đầu tiên, hãy tiếp tục và tạo một dự án, đặt tên tùy ý, sau đó cài đặt các Pod framework này vào dự án của bạn. Bạn cũng cần chỉnh sửa key NSAllowsArbitraryLoads thành YES trong dictionary NSAppTransportSecurity trong file info.plist để chỉ định domain nào được miễn trừ khỏi các quy tắc bạn định nghĩa cho App Transport Security. Trong trường hợp của chúng ta, đây là domain giphy.
Cho phép request HTTP chỉ cho domain giphy
1 | <key>NSAppTransportSecurity</key> |
Hoặc cho phép request HTTP cho tất cả domain, đây không phải là một ý tưởng tốt.
1 | <key>NSAppTransportSecurity</key> |
Hãy tạo một class có tên ImageLoader. Class này chứa hai phương thức giúp chúng ta tìm kiếm và tải ảnh gif từ server Giphy.
1 | // |
Phiên bản đầu tiên: Sử dụng callback
Đầu tiên, chúng ta cần định nghĩa hai callback, sẽ được truyền vào các phương thức fetchImage và downloadImage.
1 | public typealias FetchImageBlock = (URL?, Error?) -> Void |
Sau đó, chúng ta triển khai hai phương thức này:
fetchImagenhận keyword và callback làm tham số, gửi request đến server Giphy để truy vấn tất cả ảnh khớp với keyword, lấy cái đầu tiên và cuối cùng trả về url tải xuống qua callback.downloadImagenhận url và callback làm tham số, sau đó sử dụng frameworkAlamofiređể tải ảnh. Cuối cùng, trả về url đích, nơi ảnh được lưu, qua callback.
1 | func fetchImage(keyword: String, callback: @escaping FetchImageBlock) { |
1 | func downloadImage(url: URL, callback: @escaping DownloadImageBlock) { |
Bên trong view controller chính, hãy định nghĩa một phương thức gọi là searchImageWithKeyword. Phương thức này nhận keyword làm tham số, sau đó truyền tham số vào phương thức fetchImage của một instance của class ImageLoader. Chúng ta cũng cần truyền một callback để xử lý kết quả.
Bên trong callback fetchImage, hãy kiểm tra xem có lỗi nào không. Nếu có, chúng ta dừng gọi phương thức tiếp theo, downloadImage. Ngược lại, chúng ta gọi downloadImage của đối tượng imageLoader. Sau đó truyền url và callback làm tham số.
Bên trong callback downloadImage, hãy kiểm tra xem có lỗi nào không. Nếu có, chúng ta dừng gọi cái tiếp theo. Ngược lại, chúng ta cập nhật image view trên main view bằng cách gọi phương thức updateImageAtURL.
1 | func searchImageWithKeyword(keyword: String) { |
1 | func updateImageAtURL(url: URL) { |
Như bạn thấy, searchImageWithKeyword khá phức tạp với nhiều câu lệnh if và else bên trong phương thức. Chúng ta phải kiểm tra lỗi trong nhiều dòng code. Hãy tưởng tượng nó sẽ phức tạp như thế nào nếu chúng ta có nhiều hơn ba phương thức bên trong nó?

Build và chạy dự án. Nhập keyword bạn muốn tìm kiếm trên server Giphy, nhấn nút tìm kiếm và bạn sẽ thấy kết quả đầu tiên.
Giải pháp tốt hơn: Sử dụng Bolts
Bolts là một framework được thiết kế bởi Parse và Facebook, tôi sử dụng nó để tạo các phương thức bất đồng bộ, mà không sử dụng callback. Framework Bolts cho phép chúng ta viết code như một chuỗi các hành động dựa trên các sự kiện.
1 | func fetchImage(keyword: String) -> Task<URL>! { |
1 | func downloadImage(url: URL) -> Task<URL>! { |
Hãy xem searchImageWithKeyword sẽ đơn giản như thế nào khi sử dụng Bolts.
1 | func searchImageWithKeyword(keyword: String) { |
Build và chạy dự án, không có gì thay đổi. Nhưng code dễ đọc hơn cái đầu tiên, phải không? Chúng ta tập hợp tất cả lỗi ở một nơi, cũng tách biệt việc xử lý lỗi và code thành công.
Giải pháp tốt hơn nhiều: Sử dụng PromiseKit
Một điều tôi không thích về framework Bolts là thiếu tài liệu và dự án mẫu. Khi tôi lần đầu sử dụng framework Bolts, tôi rất khó làm quen với các API của đối tượng Task.
Tại hội nghị Swift Summit 2017, có một diễn giả đã giới thiệu một Framework để xử lý các phương thức async, PromiseKit. Sau hội nghị, tôi đã thay thế code sử dụng framework Bolts bằng PromiseKit trong các dự án của công ty tôi. Tôi nhận ra code của tôi bây giờ dễ đọc hơn. Tôi nghĩ cách viết của PromiseKit sẽ quen thuộc hơn với các developer so với cách viết của Bolts, đặc biệt là những người đã làm việc với Javascript như tôi.
Một phương thức async được tạo bằng PromiseKit trả về một Promise generic mới, đây là class chính được cung cấp bởi PromiseKit. Constructor của nó nhận một khối thực thi đơn giản với hai tham số:
- fulfill: Một hàm để gọi khi giá trị mong muốn sẵn sàng để hoàn thành promise.
- reject: Một hàm để gọi nếu có lỗi.
Hãy áp dụng PromiseKit vào dự án của chúng ta
1 | func fetchImage(keyword: String) -> Promise<URL> { |
1 | func downloadImage(url: URL) -> Promise<URL> { |
Và kết quả cuối cùng, code đẹp làm sao! :))
1 | func searchImageWithKeyword(keyword: String) { |
Một tính năng mà tôi thấy rất thú vị trong cả hai framework, Bolts và PromiseKit, là chúng cho phép code của chúng ta chạy trên một thread nhất định (Main thread hoặc background thread). Đây là một tính năng tuyệt vời vì hầu hết công việc được thực hiện trong view controller là để cập nhật UI. Đôi khi, các tác vụ chạy lâu được xử lý tốt nhất trên background thread, để không làm nghẽn UI. Để biết thêm chi tiết về tính năng Thread này, vui lòng tham khảo tài liệu của họ: #Threading
Kết luận
Vì tôi đang làm việc với CoreBluetooth, tôi thường phải làm việc với các phương thức async. Quá nhiều callback làm dự án của tôi khó hiểu hơn và khó debug nếu có lỗi xảy ra. Promise làm code của tôi trở thành một cô gái đẹp hơn ;).
Bạn có thể tải dự án mẫu hoàn chỉnh tại đây.
Hãy để lại bình luận về bài viết của tôi.