+2

Getting Started with PromiseKit in iOS

Introduction

Là một programmer chắc chắn các bạn không xa lại gì với khái niệm Asynchronous . Đây luôn là vấn đề đau đầu với mỗi lập trình viên vì bạn cần phải xử lý một cách cực kì cẩn thận nếu không muốn chương trình của mình nằm ngoài tầm kiểm soát với completion handler . Hơn nữa việc debug khá khó khăn khi bạn phải nắm được luồng xử lý. Vì thế chúng ta có khái niệm Promises . Promises cho phép bạn xử lý Asynchronous bằng các viết các đoạn mã theo một loạt các hành động sẽ diễn ra dựa trên các sự kiện biết trước.

Trong iOS chúng ta sẽ phải xử lý rất nhiều delegatescallbacks

- Y manages X.
- Tell Y to get X.
- Y notifies its delegate when X is available.

Promises sẽ đơn giản hoá nó

When X is available, do Y.

PromiseKit && Callback Hell

Mình có 1 yêu cầu đơn giản đó là :

  • Gọi API để lấy về 1 random GIF url
  • Sau đó gọi API download GIF image và thể hiện lên UIImageView

Để giải quyết yêu cầu này, chúng ta sẽ viết như sau :

SynapseAPI.fetchRandomGifUrl(forSearchQuery: "Dota2") { imageUrlString, error in
  if error {
    print(error.localizedDescription)
    throw error
  }
  self.fetchImage(forImageUrl: imageUrlString) { imageData, error in
    if error {
      print(error.localizedDescription)
      throw error
    }
    self.attachImage(withImageData: imageData)
  }
}

Các bạn có thể thấy đầu tiên chúng ta cần phải gọi fetchRandomGifUrl . Xử lý dữ liệu trả về trong callback là thành công hay lỗi, nếu thành công thì sẽ gọi tiếp fetchImage . Tiếp tục xử lý dự liệu trả về trong callback của hàm này.

Cùng một công việc cần làm như vậy, các bạn hãy xem nếu sử dụng PromiseKit như thế nào nhé.

PromiseKitHelper.fetchRandomGifUrl(forSearchQuery: "Dota2")
        .then { imageUrlString in
            self.fetchImage(forImageUrl: imageUrlString)
        }.then { imageData in
            self.attachImage(withImageData: imageData)
        }.catch { error in
            print(error)
        }

Bạn sẽ thích viết như thế nào hơn? Callback hay Promises ? Có thể thấy cùng sử dụng 2 hàm để thực hiện 2 công việc nhưng PromiseKit trả về Promises thay vì bắt các sự kiện Callback. Sử dụng .then() để chờ đợi request thực hiện xong trước khi chuyển qua công việc tiếp theo. Và sử dụng .catch() để bắt tất cả lỗi nếu xảy ra. Không còn phải sử dụng lặp lại if,else để bắt lỗi nữa.

How to use PromiseKit

Trước hết để sử dụng PromiseKit bạn phải install third party này từ github về project của bạn đã 😃
Tiếp theo chúng ta tạo các hàm sử dụng Promises trong PromiseKitHelper class

func fetchRandomGifUrl(forSearchQuery query: String) -> Promise<String> {
        let params = ["api_key": self.APIKey, "q": query]
        // Return a Promise for the caller of this function to use.
        return Promise { fulfill, reject in
            // Inside the Promise, make an HTTP request to the Giphy API.
            Alamofire.request("", parameters: params)
                .responseJSON { response in
                    if let json = response.result.value as? [String: Any] {
                        if let imageUrlString = json["url"] as? String {
                            fulfill(imageUrlString)
                        } else {
                            reject(response.error!)
                        }
                }
            }
        }
    }

Mình trả về Promise chứ không phải là completion handler trong Callback.
Khi kết quả trả về thành công truyền vào hàm fulfill và ngược lại là thông báo lỗi cho hàm reject

Từ url image link có được chúng ta phải download để hiển hiện lên trên máy. Do đó mình viết tiếp hàm fetchImage

func fetchImage(forImageUrl imageUrlString: String) -> Promise<Data> {
        // Return a Promise for the caller of this function to use.
        return Promise { fulfill, reject in
            // Make an HTTP request to download the image.
            Alamofire.request(imageUrlString).responseData { response in
                if let imageData = response.result.value {
                    print("image downloaded")
                    // Pass the image data to the next function.
                    fulfill(imageData)
                } else {
                    // Reject the Promise if something went wrong.
                    reject(response.error!)
                }
            }
        }
    }

Hàm này khá đơn giản với việc mình chỉ truyền lên imageUrlString và nhận về Promise<Data> . Và cuối cùng là hàm hiển thị image

func attachImage(withImageData imageData: Data) -> Void {
  let image = UIImage(gifData: imageData)
  self.snesGifImageView.setGifImage(image)
}

Mình đã xây dựng xong các promise funcs. Có thể thấy là khá đơn giản đúng không.

Why is PromiseKit

Code cleaner

Rx

service.register(for: registation)
.subscribe(onNext: {
    value in
},
onError: { err in

})
.addDisposableTo(disposeBag)

Promise

service.register(for: registation)
.then(
    value -> () in
}
.catch {
    err in
}

Hỗ trợ mạnh mẽ

CLLocationManager.promise().then { location in
    // pod "PromiseKit/CoreLocation"
}
MKDirections(/*…*/).calculate().then { directionsResponse in
    // pod "PromiseKit/MapKit"
}
URLSession.shared.POST(url, formData: params).asDictionary().then { json in
    // pod "PromiseKit/OMGHTTPURLRQ"
}
Alamofire.request(url, withMethod: .GET).responseJSON().then { json in
    // pod "PromiseKit/Alamofire"
}
CKContainer.publicCloudDatabase.fetchUserRecord().then { user in
    // pod "PromiseKit/CloudKit"
}
promiseViewController(UIImagePickerController()).then { image in
    // pod "PromiseKit/UIKit"
    // --> included in the default "PromiseKit" pod
}
NSNotificationCenter.default.observe(once: notificationName).then { obj in
    // pod "PromiseKit/Foundation"
    // --> included in the default "PromiseKit" pod
}

Conclusion

  • Trong giới hạn tìm hiểu của mình về PromiseKit mới chỉ dừng lại ở mức Getting Started. Mình sẽ cố gắng đi sâu vào chi tiết hơn ở các bài viết sau. Và đồng thời sẽ đưa ra những so sánh giữa việc dùng PromiseKit hay RxSwift.
  • Happy Coding!

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí