Xây dựng swift package để gọi network trong swift

83

Bài viết được sự cho phép của tác giả Lê Xuân Quỳnh

Tuần trước, chúng ta đã xem qua 1 cách custom về thư viện bàn phím hiển thị gợi ý tiền Việt Nam. Hôm nay tôi sẽ hướng dẫn cách tạo 1 thư viện để gọi network dạng restful API. Thư viện tên là Qnetwork. Bạn có thể tải trên github tại đường dẫn:

https://github.com/lexuanquynh/QNetwork

Thư viện này dùng để làm gì?

Đầu tiên trước khi tiến hành xây dựng, hãy tải phần demo của tôi tại đường dẫn:

https://github.com/lexuanquynh/QNetworkDemo

Đầu tiên, bạn cần biết rằng tôi xây dựng thư viện này tốt nhất cho kiến trúc MVVM. Tuy nhiên bạn vẫn có thể sử dụng nó cho các architecture khác mà bạn muốn. Nếu hiểu theo cách đơn giản, bạn dùng thư viện này, bạn tạo các service để gọi API. Sau đó, bạn dùng nó để gọi bất cứ ở đâu bạn muốn.

  Các cách sử dụng AS, AS?, AS! một cách hiệu quả và an toàn trong code Swift
  Các cải tiến của Java cho Switch statement từ Java 12

Xem thêm các việc làm Swift lương cao trên TopDev

Vậy cách sử dụng nó như nào? Hãy xem phần code demo của tôi. Ở ví dụ này tôi sẽ tiến hành gọi API của github:

curl \
  -H "Accept: application/vnd.github.v3+json" \
  https://api.github.com/search/repositories

Trong 1 bài viết trước đây, tôi đã giới thiệu khá kỹ càng cách viết API cho nó. Các bước tiến hành như sau:

  • Tạo 1 module Github. Mỗi module sẽ phục vụ cho 1 màn hình riêng biệt. Trong thiết kế MVVM tôi hay làm như vậy, module bao gồm View, Model, servive gọi API và view model. Bạn có thể mở souce code ở trên ra xem.
  • Tạo 1 file GithubAPI. File này chứa cụm API cần gọi. Thông thường trong ứng dụng của tôi sẽ có nhiều file dạng *API. Mỗi 1 file chứa 1 cụm, ví dụ AuthenAPI sẽ chứa cụm đăng nhập, đăng xuất. FeedAPI chứa cụm API liên quan đến feed. Cách đặt tên sẽ sát nghĩa nó làm gì. Mô tả file này như sau:
//
//  GithubAPI.swift
//  QNetworkDemo
//
//  Created by Xuân Quỳnh Lê on 2021/06/26.
//

import Foundation
import QNetwork
import Moya

enum GithubAPI {
    case searchRepositories(q: String, sort: String, order: String, page: Int)
}

extension GithubAPI: TargetType {
    var baseURL: URL {
        let url = URL(string: Configs.Network.baseUrl)!
        return url
    }

    var path: String {
        switch self {
        case .searchRepositories:
            return "search/repositories"
        }
    }

    var method: Moya.Method {
        return .get
    }

    var sampleData: Data {
        var dataUrl: URL?

        switch self {
        case .searchRepositories:
            if let file = Bundle.main.url(forResource: "SearchRepositoriesResponse", withExtension: "json") {
                dataUrl = file
            }
        }
        if let url = dataUrl, let data = try? Data(contentsOf: url) {
            return data
        }

        return Data()
    }

    var task: Task {
        switch self {
        case .searchRepositories:
            if let parameters = parameters {
                return .requestParameters(parameters: parameters, encoding: parameterEncoding)
            }
        }
        return .requestPlain
    }

    var headers: [String: String]? {
        return ["Content-type": "application/json"]
    }

    var parameters: [String: Any]? {
        var params: [String: Any] = [:]

        switch self {
        case .searchRepositories(let q, let sort, let order, let page):
            params["q"] = q
            params["sort"] = sort
            params["order"] = order
            params["page"] = page
        }

        return params
    }

    // For json encode. Use in post request
    var jsonEncoding: JSONEncoding {
        return JSONEncoding.default
    }

    // For param encode. Use in get request
    var parameterEncoding: ParameterEncoding {
        return URLEncoding.default
    }
}
  • Sau đó bạn tạo tiếp file GithubSearchService. Ở đây bạn cần truyền 1 struct Codable để lúc API có response, bạn cần đưa nó vào và chuyển json sang struct này. Cụ thể là struct GithubSearchResponse:
// MARK: - GithubSearchResponse
struct GithubSearchResponse: Codable {
    let totalCount: Int?
    let incompleteResults: Bool
    let items: [GithubSearchItem]?

    enum CodingKeys: String, CodingKey {
        case totalCount = "total_count"
        case incompleteResults = "incomplete_results"
        case items
    }
}

// MARK: - Item
struct GithubSearchItem: Codable {
    let id: Int
    let name: String?
    let htmlURL: String?
    let itemDescription: String?

    enum CodingKeys: String, CodingKey {
        case id, name
        case htmlURL = "html_url"
        case itemDescription = "description"
    }
}

Nhiều bạn hỏi tôi sao có thể từ json chuyển sang struct được như trên. Tôi là 1 người lười biếng, nên tôi hay vào trang này để chuyển:
https://app.quicktype.io

Sau đó bạn chỉ cần chỉnh sửa tên cho hợp lý là được. Bạn làm nhiều sẽ quen tay thôi.

  • Sau khi xong cụm service ở trên, bạn cần tạo GithubViewModel:
import Foundation

class GithubViewModel {
    // Service call API
    let service: GithubSearchService!
    // Callback to view
    var needReloadTableView: (() -> Void)?
    var needShowError: ((String) -> Void)?
    var needSetStateBottomIndicatorView: ((_ show: Bool) -> Void)?

    private var page: Int = 0
    private var language = ""
    private var incompleteResults = false
    
    // Datasource
    private var githubSearchItem: [GithubSearchItem] = []

    init() {
        // Turn on is test is true if you need test for API
        self.service = GithubSearchService(isTest: false)
    }

    /// Clear tableview data source
    func clearTableView() {
        self.page = 0
        self.incompleteResults = false
        self.githubSearchItem.removeAll()
        self.needReloadTableView?()
    }

    /// Request repositories
    func requestRepositories(language: String, loadMore: Bool = false) {
        // Check when load more
        if self.incompleteResults {
            return
        }
        if !loadMore {
            self.page = 0
            self.githubSearchItem.removeAll()
        }

        self.language = language

        // Default param
        let sort = "stars"
        let order = "desc"

        self.service.searchRepositories(language: language, sort: sort, order: order, page: self.page) { [weak self] result in
            guard let strongSelf = self else { return }
            // Check when load more
            if loadMore {
                strongSelf.needSetStateBottomIndicatorView?(false)
            }

            switch result {
            case .success(let githubResponse):
                strongSelf.incompleteResults = githubResponse.incompleteResults
                if let items = githubResponse.items {
                    items.forEach( {strongSelf.githubSearchItem.append( $0 )})
                }
                strongSelf.needReloadTableView?()
            case .failure(let error):
                strongSelf.needShowError?(error.description)
            }
        }
    }

    func numberOfRowsInSection(section: Int) -> Int {
        return githubSearchItem.count
    }

    func cellForRowAt(indexPath: IndexPath) -> GithubSearchItem {
        // Check if the last row number is the same as the last current data element
            if indexPath.row == self.githubSearchItem.count - 1 {
                self.page += 1
                self.requestRepositories(language: language, loadMore: true)
                self.needSetStateBottomIndicatorView?(true)
            }

        return githubSearchItem[indexPath.row]
    }
}

Nhiều bạn sẽ tò mò tại sao tôi lại viết được như trên. Cũng tương tự, tôi hay tải source của người khác về xem. Sau đó tôi clone y hệt, người ta viết gì, tôi viết nấy. Lâu dần quen tay, có phản xạ. Cuối cùng là hiểu được ý nghĩa mà người ta làm. Dù sao thì thực hành vẫn là cách tốt nhất để học lập trình.

Xây dựng QNetwork như nào?

Việc này khá là mù mờ nếu như không có video. Thú thật tôi đã xem video hướng dẫn của 1 developer khác ở đường dẫn này:
https://www.youtube.com/watch?v=xu9oeCAS8aA

Bạn xem kỹ video, làm theo anh ấy, bạn sẽ tạo được 1 swift package và có thể đưa cho người khác sử dụng thông qua 1 đường dẫn. Với Qnetwork, đơn giản bạn chỉ cần thêm đường dẫn này vào phần swift package của bạn(Xcode ➞ File ➞ New ➞ Swift Package):

https://github.com/lexuanquynh/QNetwork.git

Từ nay tôi sẽ chỉ cần 1 đường link, sau đó thêm vào, và có thể dễ dàng triển khai lớp network service của mình.

Nếu bạn có câu hỏi gì, hãy vào fan page của trang tại địa chỉ:

https://www.facebook.com/codetoanbug

Hãy chia sẻ cho ai đó nếu bạn thấy bài viết có ích.

Bài viết gốc được đăng tải tại codetoanbug.com

Có thể bạn quan tâm:

Xem thêm Việc làm IT hấp dẫn trên TopDev