+2

Swift Tutorial - sử dụng JSON trong ios

Với bài viết này,sẽ dùng demo iTunes Search API cho iTunes Store được public bởi Apple, sử dụng gói JSON kết quả, phân tích chúng, tạo Dictionary và đưa những thông tin đó vào Table View. Sau đó, chúng

Tạo và Kết nối đến UI (Giao diện người dùng)

Việc đầu tiê cần làm là tạo một tableView. tiếp theo thêm đoạn code sau vào trong file ViewController.swift đặt nó bên dưới

@IBOutlet var appsTableView : UITableView?

đoạn code trên cho phép một kết nối đến Table View nằm trong Storyboard với một biến appsTableView. Hãy lưu file này lại và mở Storyboard lên. Chọn đối tượng View Controller

Screen Shot 2016-01-29 at 10.06.09.png

Tiếp theo thêm một biến cho TableView. Bằng cách thêm phía dưới ViewController, sử dụng đoạn code sau:

var tableData = []

Tạo API request

Bây giờ đã có UI được kết nối, tiếp theo tạo một hàm mới searchItunesFor(searchTem:String).Sử dụng hàm này khi một có một request tìm kiếm xuất hiện. Sử dụng đoạn code như dưới đây

func searchItunesFor(searchTerm: String) {

    // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
    let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)

    // Now escape anything else that isn't URL-friendly
    if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
        let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
        let url: NSURL = NSURL(string: urlPath)
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
            println("Task completed")
            if(error != nil) {
                // If there is an error in the web request, print it to the console
                println(error.localizedDescription)
            }
            var err: NSError?

            var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
            if(err != nil) {
                // If there is an error parsing JSON, print it to the console
                println("JSON Error \(err!.localizedDescription)")
            }
            let results: NSArray = jsonResult["results"] as NSArray
            dispatch_async(dispatch_get_main_queue(), {
                self.tableData = results
                self.appsTableView!.reloadData()
            })
        })

        task.resume()
        }
}

Tiếp theo cần phải chỉnh sửa lại các thuật ngữ tìm kiếm trong API search tương tự dưới dạng sau: “Một+Hai+Ba...” thay vì với dạng “Một%Hai%Ba...”. Vậy để thay thế cần sử dụng một phương pháp có tên là NSString. Nó sẽ gọi stringByReplacingOccurencesOfString và trả về thay đổi của từ khóa tìm kiếm đầu vào với toàn bộ khoảng trắng(space) được thay thế bằng dấu cộng (+)

Tiếp theo cần phải loại bỏ những ký tự mà không được phép nằm trong một đường dẫn (URL)

Hai dòng tiếp theo định nghĩa đối tượng NSURL để có thể được sử dụng như là một Request URL cho iOS Networking API

Đây là hai dòng này rất quan trọng:

let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in

Dòng đầu tiên lấy đối tượng NSURLSession mặc định được sử dụng trong toàn bộ networking calls.

Sau đó dòng thứ hai sẽ khởi tạo kết nối sẽ được sử dụng để gửi request.

dataTaskWithURL chỉ được chạy khi hoàn thành các yêu cầu trước đó. Tại đây tiếp tục kiểm tra lỗi xảy ra với các response tiếp đó parse gói JSON và gọi phương thức delegate didReceiveAPIResults.

Cuối cùng, task.resume() sẽ bắt đầu request

Bởi vì nhiệm vụ này xảy ra trong background chúng ta cần phải vào foreground trước khi UI được cập nhật Vì vậy cần phải sử dụng dispatch_async để di chuyển trở lại vào các thread chính, và load lại table view.

Tạo lời gọi API(API CALL)

Khi đã đã có một method cái bắt đầu việc tìm kiếm iTunes tiếp tục thêm đoạn code này vào cuối cùng của phương thức viewDidLoad

searchItunesFor("JQ Software")

Nó sẽ kìm kiếm tất cả những sản phẩm có chứa những từ khoá đã nhập vào trên iTunes store.

Nhận kết quả trả về

Đoạn code dùng để request tìm kiếm đã hoàn thành và tất cả những dữ liệu đã được trả về. didReceiveAPIResults được gọi để sử dụng dữ liệu đó trong app .

Một method ở đây sử dụng NSJSONSerialization để chuyển đổi dữ liệu thô sang những thông tin hữu ích và được tạo chỉ mục từ iTunes. Bây giờ có thể đặt tableData riêng self.tableData object như là một kết quả của quá trình tìm kiếm. Và refresh lại appsTableView để hiển thị kết quả.

Cập nhật Table View UI cập nhật bằng cách sử dụng dữ liệu mà chúng đã lấy về từ web.

func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
    return tableData.count
}

func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
    let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")

    let rowData: NSDictionary = self.tableData[indexPath.row] as NSDictionary

    cell.textLabel.text = rowData["trackName"] as String

    // Grab the artworkUrl60 key to get an image URL for the app's thumbnail
    let urlString: NSString = rowData["artworkUrl60"] as NSString
    let imgURL: NSURL = NSURL(string: urlString)

    // Download an NSData representation of the image at the URL
    let imgData: NSData = NSData(contentsOfURL: imgURL)
    cell.imageView.image = UIImage(data: imgData)

    // Get the formatted price string for display in the subtitle
    let formattedPrice: NSString = rowData["formattedPrice"] as NSString

    cell.detailTextLabel.text = formattedPrice

    return cell
}

numberOfRowsInSection trả về số lượng các kết quả của object từ tableData, nó đã thiết lập trước trong method connectionDidFinishLoading. Trong trường hợp này thì cellForRowAtIndexPath sẽ trả lại số lượng cell để lấy ra các thông tin thông tin là tên ca khúc, url của ca khúc và giá cả.

Chia code

Đầu tiên đổi tên View Controller với một cái tên có ý nghĩa và mở file ViewController.swift và thay tất cả những object liên quan đến ViewController với cái tên mới vừa thay đổi và tiếp tục đổi tên mới trong file SearchResultsViewController.swift

Nếu như sau đó chạy app ngay thì kết quả bạn sẽ gặp là một lỗi crash. bởi vì storyboard chưa được cập nhật để biết về những tên class mới. Vậy nên tiếp mở Main.storyboard và chọn View Controller object hình dưới đây (Bên trái) và lựa chọn Identity Inspector (bên phải, nút thứ 3)

Từ đây thay tên class từ “View Controller” và “SearchResultViewController”. chạy lại và Kiểm tra ứng dụng sẽ chạy bình thường và tục các bước tiếp theo.

Screen Shot 2016-01-29 at 10.13.10.png

Tiếp theo di chuyển code API ra lớp riêng của nó. Click chuột phải vào Xcode navigation pane và chọn New File... Việc này sẽ khởi tạo một file Swift mới (bên dưới iOS → Source Navigation option)

Screen Shot 2016-01-29 at 10.15.42.png

Điều này xử lý và nắm tất cả công viecj của API, và sẽ gọi là APIController. Tiếp theo lấy phương thức searchItunesFor() rồi cut chúng ra khỏi Search Controller và paster chúng vào APIController bên trong một lớp mới của APIController.

File APIController hoàn chỉnh sẽ giống như sau:

import Foundation

class APIController {

    func searchItunesFor(searchTerm: String) {

        // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
        let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)

        // Now escape anything else that isn't URL-friendly
        if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
            let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
            let url: NSURL = NSURL(string: urlPath)
            let session = NSURLSession.sharedSession()
            let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
                println("Task completed")
                if(error != nil) {
                    // If there is an error in the web request, print it to the console
                    println(error.localizedDescription)
                }
                var err: NSError?

                var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
                if(err != nil) {
                    // If there is an error parsing JSON, print it to the console
                    println("JSON Error \(err!.localizedDescription)")
                }
                let results: NSArray = jsonResult["results"] as NSArray
                dispatch_async(dispatch_get_main_queue(), {
                    self.tableData = results
                    self.appsTableView!.reloadData()
                })
            })

            task.resume()
        }
    }

}

Nếu như build chương trình ngay thì sẽ thấy ba lỗi như dứoi đây:

1) searchItunesFor() undefined trong SearchResultsViewController.
2) self.tableData undefined trong APIController.
3) self.appsTableView  undefined trong APIController.

Để khắc phục với điều này cần để cho các đối tượng này reference với nhau bằng cách khởi tạo APIController một object con của SearchResultsViewController.

var api = APIController()

Tiếp theo thay đổi dòng code để gọi hàm “calls searchItunesFor()” thành

api.searchItunesFor("Angry Birds")

để cho các APIController hiểu SearchResultsViewController này là các delegate. tiếp theo thiết lập các delegate

api.delegate = self

Việc này sẽ giải quyết lỗi đầu tiên, nhưng bây giờ cần phải có được kết quả của APIController trả về cho SearchResultsViewController. Để Controller API có giải quyết với gọi API, tiếp theo định nghĩa một giao thức(protocol) để Views có thể đăng ký để nhận được kết quả!

Đinh nghĩa API protocol

dispatch_async(dispatch_get_main_queue(), {  // DELETE ME
    self.tableData = results                 // DELETE ME
    self.appsTableView!.reloadData()         // DELETE ME
})
```                                          // DELETE ME

Bên trên class khởi tạo  APIController thêm một giao thức để thiết lập hàm didReceiveAPIResults() như một yêu cầu cần thực hiện.

```Swift
protocol APIControllerProtocol {
   func didReceiveAPIResults(results: NSDictionary)
}

Tuân thủ protocol

Bây giờ hãy thay đổi SearchResultsViewController tuân theo các protocol:

class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, APIControllerProtocol {```Swift

Việc buil app lúc này sẽ cho ra một lỗi mà SearchResultsViewController không phù hợp với các APIControllerProtocol. Đúng vậy, đó là những gì mà một giao thưc đã làm. Nó làm cho các trình biên dịch để đưa ra lỗi nếu có ý định thực hiện một giao thức, nhưng không hoàn toàn làm điều đó. Trong trường hợp này barfing đang thiếu các didReceiveAPIResults().

Để thêm, chỉ cần add function sau vào trong lớp SearchResultsViewController. Nó thực sự là giống với nội dung của dataTaskWithURL.

func didReceiveAPIResults(results: NSDictionary) {
  var resultsArr: NSArray = results["results"] as NSArray
  dispatch_async(dispatch_get_main_queue(), {
    self.tableData = resultsArr
    self.appsTableView!.reloadData()
  })
}

Sử dụng Protocol Ở file APIController.swift, tiếp tục thêm các delegate và xây dựng một contructor

var delegate: APIControllerProtocol?

init() {
}

dấu chấm hỏi(?) ở trên là vì delegate là một giá trị tùy chọn(có thể có giá trị hoặc không). Nếu không có dấu hỏi, thì sẽ nhận được một lỗi biên dịch vì không một giá trị ban đầu cho biến này. Các đối tượng delegate có thể được của bất kỳ class nào ở đây, miễn là nó tuân thủ các APIControllerProtocol bằng cách xác định method didReceiveAPIResults() như đã làm trong SearchResultsViewController.

Bây giờ có biến delegate được add thêm, và trở trở lại SearchResultsViewController và trong method viewDidLoa để thiết lập các delegate cho api controller chính nó, nó sẽ nhận lời gọi delegate function

self.api.delegate = self

Cuối cùng, trong việc kết thúc dataTaskWithURL bên trong của method searchItunesFor () , tiếp tục thêm một giao thức nơi tableview khi load lại.việc tải lại giao diện người dùng tới các delegate, trong trường hợp này là SearchResultsViewController. “let results: NSArray = jsonResult["results"] as NSArray”.

self.delegate?.didReceiveAPIResults(jsonResult)

Dưới đây là toàn bộ method

func searchItunesFor(searchTerm: String) {

    // The iTunes API wants multiple terms separated by + symbols, so replace spaces with + signs
    let itunesSearchTerm = searchTerm.stringByReplacingOccurrencesOfString(" ", withString: "+", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)

    // Now escape anything else that isn't URL-friendly
    if let escapedSearchTerm = itunesSearchTerm.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
        let urlPath = "http://itunes.apple.com/search?term=\(escapedSearchTerm)&media=software"
        let url: NSURL = NSURL(string: urlPath)
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
            println("Task completed")
            if(error != nil) {
                // If there is an error in the web request, print it to the console
                println(error.localizedDescription)
            }
            var err: NSError?

            var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &err) as NSDictionary
            if(err != nil) {
                // If there is an error parsing JSON, print it to the console
                println("JSON Error \(err!.localizedDescription)")
            }
            let results: NSArray = jsonResult["results"] as NSArray
            self.delegate?.didReceiveAPIResults(jsonResult)
        })

        task.resume()
    }
}

download source code


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í