Alamofire - Elegant networking in swift. Part 1: Getting started
Như chúng ta đã biết thì AFNetworking là một trong những thư viện phổ biến nhất được viết cho iOS và OSX . Năm 2012 nó đã nhận được danh hiệu 2012 Best iOS Library Award và nó là thư viện mã nguồn mở được sử dụng nhiều nhất trong các dự án (tại Github với hơn 14K stars và 4K folks) Gần ...
Như chúng ta đã biết thì AFNetworking là một trong những thư viện phổ biến nhất được viết cho iOS và OSX. Năm 2012 nó đã nhận được danh hiệu 2012 Best iOS Library Award và nó là thư viện mã nguồn mở được sử dụng nhiều nhất trong các dự án (tại Github với hơn 14K stars và 4K folks)
Gần đây, cùng tác giả Mattt Thompson đã phát hành một thư viện networking mới giống như AFNetworking nhưng được thiết kế đặc biệt để phù hợp với ngôn ngữ và convention Swift. Nó có tên là Alamofire.
Alamofire chính là tên viết tắt của AF trong AFNetworking và thư viện mới này sử dụng convention của SWift. Tôi sẽ dành 2 part để viết về thư viện mã nguồn mở Alamofire trong series này.
-
Trong part 1 này tôi sẽ hướng dẫn các bạn sử dụng thư viện Alamofire để xây dựng ứng dụng Photo gallery với nguồn ảnh được lấy từ https://500px.com. Trong quá trình làm bạn sẽ hiểu được về các thành phần quan trọng của Alamofire và một vài thứ quan trọng khác nhằm quản lý các network request trong ứng dụng của bạn.
-
Part 2 sẽ tiếp tục part 1 và bạn sẽ hiểu thêm một vài chức năng khác và một số đặc trưng của Alamofire
Bắt đầu
Bạn có thể download start project theo đường link Photomania_Starter_6.4.zip để bắt đầu. Trong đó nó đã code phần giao diện, nhưng trong tutorial này bạn nên quan tâm các sử dụng Alamofire hơn là giao diện.
Bạn mở project bằng Xcode và nhìn vào file Main.storyboard
Trong ứng dụng này có sử dụng UITabBarController làm rootViewController. Tab view controller bao gồm 2 tab, mỗi tab là một UINavigationController. Tab thứ nhất cho phép người dùng duyệt các ảnh, tab thứ 2 duyệt các ảnh đã được người dùng lưu lại. Cả hai đều sử dụng UICollectionViewController để hiển thị ảnh. Và Main.storyboard chứa toàn bộ các view controller sẽ được dùng trong tutorial này.
Bạn build và run ứng dụng sẽ được như sau:
Để có thể sử dụng Alamofire mới nhất bạn truy cập tại Alamofire và click vào button download Zip. Bạn mở project và kéo folder Alamofire-master vào trong project của bạn.
Bạn mở folder folder Alamofire-master và kéo Alamofire.xcodeproj (file có icon màu xanh)
Tiếp theo bạn click vào Photomania và đảm bảo bạn đã chọn General tab. Cuộn xuống và click vào + bên dưới Embedded Binaries, chọn Alamofire.framework và click Add.
Bạn buil và run project đảm bảo không lỗi để thực tiếp các bước tiếp theo.
Tiếp theo công việc là lấy dữ liệu về với Alamofire
-
Có thể bạn đang đặt câu hỏi vì sao Alamofire lại là thư viện được dùng để thay thế mà không phải là một thư viện khác? Apple cung cấp class NSURLSession và một số class khác liên quan, tại sao chúng ta vẫn cần tới thư viện Alamofire?
-
Câu trả lời là Alamofire cũng dựa trên nền tảng NSURLSession nhưng bạn hãy thử so sánh việc sử dụng NSURLSession và Alamofire sau tutorial này xem thế nào nhé. Việc bạn viết code có sử dụng Alamofire thì dễ viết code, rõ ràng, đơn giản. Bạn có thể truy cập internet với rất ít nỗ lực, code trong sáng, dễ đọc.
Để có thể sử dụng Alamofire thì đầu tiên bạn cần phải import nó.
import Alamofire
Việc import này được thực hiện tại class mà bạn sử dụng Alamofire
Tiếp theo bạn add đoạn code sau và viewDidLoad() và sau setupView()
Alamofire.request(.GET, "https://api.500px.com/v1/photos").responseJSON() { (_, _, data, _) in println(data) }
Tôi sẽ giải thích luôn, nhưng bạn hãy build và run ứng dụng. Và bạn sẽ nhìn thấy message sau:
Optional({ error = "Consumer key missing."; status = 401; })
Tuy nó là lỗi nhưng bạn đã tạo được request đầu tiên với Alamofire và bạn đã nhận được response là file json trả về.
- Alamofire.request(:) với hai tham số là method (POST, GET, ...) và URL (dạng string)
- Thông thường chỉ đơn giản là một chuỗi các request object, ví dụ với đoạn code trên thì responseJSON() chỉ đơn giản cung cấp cho bạn một closure khi request được hoàn thành. Trong tutorial này thì response trả ra dạng JSON được parse trong console.
- Bằng cách bạn call responseJSON thì bạn sẽ nhận được phản hồi là JSON như bạn mong muốn. Trong trường hợp này thì Alamofire sẽ cố gắng trả ra cho bạn đối tượng JSON. Cuối cùng bạn có thể request danh sách các thuộc tính bằng cách sử dụng responsePropertyList hoặc get string bằng cách sử dụng responseString. Bạn sẽ biết rõ hơn trong tutorial sau.
Tiếp theo, điều quan trọng chính là nguồn dữ liệu ở đâu?
Phía trên tôi đã đề cập chúng ta sẽ lấy ảnh từ nguồn https://500px.com. Nhưng chúng ta cần có key bằng cách register. Truy cập Signup và register với một email của bạn hoặc có thể từ facebook của bạn hay một số mạng xã hội khác. Khi hoàn thành bạn hãy vào Settings và click vào register your application. Bạn sẽ thấy như hình sau:
Sau khi register xong bạn sẽ nhận được chi tiết sau:
Bạn hãy copy consumer key và add vào parameters như sau:
Alamofire.request(.GET, "https://api.500px.com/v1/photos", parameters: ["consumer_key": "PASTE_YOUR_CONSUMER_KEY_HERE"]).responseJSON() { (_, _, JSON, _) in println(JSON) }
Bạn cần đảm bảo PASTE_YOUR_CONSUMER_KEY_HERE được nhập vào là key của bạn đã copy bên trên. Bạn build và run ứng dụng sẽ thấy log từ console.
Và dưới đây là JSON:
{ "feature": "popular", "filters": { "category": false, "exclude": false }, "current_page": 1, "total_pages": 250, "total_items": 5000, "photos": [ { "id": 4910421, "name": "Orange or lemon", "description": "", . . . } }, { "id": 4905955, "name": "R E S I G N E D", "description": "From the past of Tagus River, we have History and memories, some of them abandoned and disclaimed in their margins ...", . . . } ] }
Thay print(JSON) trong viewDidLoad() thành code sau:
let photoInfos = (JSON!.valueForKey("photos") as! [NSDictionary]).filter({ ($0["nsfw"] as! Bool) == false }).map { PhotoInfo(id: $0["id"] as! Int, url: $0["image_url"] as! String) } self.photos.addObjectsFromArray(photoInfos) self.collectionView!.reloadData()
Bạn build và run ứng dụng được như sau:
Bước tiếp theo cần hiển thị dữ liệu lên giao diện
Mở PhotoBrowserCollectionViewController.swift và thêm đoạn code sau vào collectionView(_: cellForItemAtIndexPath:) trước khi return cell
let imageURL = (photos.objectAtIndex(indexPath.row) as! PhotoInfo).url Alamofire.request(.GET, imageURL).response() { (_, _, data, _) in let image = UIImage(data: data!) cell.imageView.image = image }
Bạn build và run ứng dụng:
Vậy câu hỏi đặt ra làm sao bạn có thể dễ dàng quản lý các API và khi viết code không cần copy/paste với những rủi ro không lường trước? Câu trả lời là bạn cần tạo ra một request router. Bạn mở Five100px.swift và import Alamofire.
enum Router: URLRequestConvertible { static let baseURLString = "https://api.500px.com/v1" static let consumerKey = "PASTE_YOUR_CONSUMER_KEY_HERE" case PopularPhotos(Int) case PhotoInfo(Int, ImageSize) case Comments(Int, Int) var URLRequest: NSURLRequest { let (path: String, parameters: [String: AnyObject]) = { switch self { case .PopularPhotos (let page): let params = ["consumer_key": Router.consumerKey, "page": "(page)", "feature": "popular", "rpp": "50", "include_store": "store_download", "include_states": "votes"] return ("/photos", params) case .PhotoInfo(let photoID, let imageSize): var params = ["consumer_key": Router.consumerKey, "image_size": "(imageSize.rawValue)"] return ("/photos/(photoID)", params) case .Comments(let photoID, let commentsPage): var params = ["consumer_key": Router.consumerKey, "comments": "1", "comments_page": "(commentsPage)"] return ("/photos/(photoID)/comments", params) } }() let URL = NSURL(string: Router.baseURLString) let URLRequest = NSURLRequest(URL: URL!.URLByAppendingPathComponent(path)) let encoding = Alamofire.ParameterEncoding.URL return encoding.encode(URLRequest, parameters: parameters).0 } }
Tạm thời chúng ta cần phải có Loading More Photos Bạn mở PhotoBrowserCollectionViewController.swift và thêm đoạn code dưới vào sau let refreshControl = UIRefreshControl():
var populatingPhotos = false var currentPage = 1 Và thay thế viewDidLoad bằng đoạn code sau override func viewDidLoad() { super.viewDidLoad() setupView() populatePhotos() }
Để implement handleRefresh() chúng ta có đoạn code
// 1 - load more photo khi bạn scroll 80% contentsize của scroll view override func scrollViewDidScroll(scrollView: UIScrollView) { if scrollView.contentOffset.y + view.frame.size.height > scrollView.contentSize.height * 0.8 { populatePhotos() } } func populatePhotos() { // 2 - Chỉ load page hiện tại tránh load next page if populatingPhotos { return } populatingPhotos = true // 3 - return 50 photo cho mỗi request Alamofire.request(Five100px.Router.PopularPhotos(self.currentPage)).responseJSON() { (_, _, JSON, error) in if error == nil { // 4 - DISPATCH_QUEUE_PRIORITY_HIGH dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) { // 5, 6, 7 - filter data let photoInfos = ((JSON as! NSDictionary).valueForKey("photos") as! [NSDictionary]).filter({ ($0["nsfw"] as! Bool) == false }).map { PhotoInfo(id: $0["id"] as! Int, url: $0["image_url"] as! String) } // 8 - collectionView update count number let lastItem = self.photos.count // 9 - add photoInfos to array photos self.photos.addObjectsFromArray(photoInfos) // 10 create NSIndexPath to insert CollectionView let indexPaths = (lastItem..<self.photos.count).map { NSIndexPath(forItem: $0, inSection: 0) } // 11 - Update UI use insertItemsAtIndexPaths dispatch_async(dispatch_get_main_queue()) { self.collectionView!.insertItemsAtIndexPaths(indexPaths) } self.currentPage++ } } self.populatingPhotos = false } }
Bạn build và run ứng dụng
Nguồn tham khảo beginning-alamofire-tutorial