Getting Started with PromiseKit in iOS
Bài đăng này đã không được cập nhật trong 7 năm
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 delegates
và callbacks
- 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ứcGetting 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ùngPromiseKit
hayRxSwift
. - Happy Coding!
All rights reserved