Tạo lớp APIClient (Swift 3)
swift
68
ios
54
White

VietHQ viết ngày 29/05/2017

1. Giới thiệu

Từ hồi mới bắt đầu làm IOS, thằng nào cũng hỏi mình có biết sử dụng AFNetworking không? Khổ nỗi lúc đó, mình mới chuyển từ làm game sang, rất ít dùng đến lib, đa số tự viết nên lơ tơ mơ không biết chúng nó nói đến cái gì. Dùng anh google tìm hiểu thì mình mới ngộ ra AFNetworking là một toy wrap lại urlsession, hỗ trợ developer thao tác nhanh gọn, đỡ mất công viết đi viết lại những đoạn code thủ tục (lib đã hỗ trợ bạn làm việc đó). Từ đó project nào mình cũng gắn toy vào.

Tuy nhiên AFNetworking được viết bằng obj-c và không có phiên bản swift. May mắn thay, ta có một lib thay thế, phổ biến không kém trên nền swift.
Thậm chí chúng nó còn quảng cáo qua lại cho nhau :smile:

Programming in Swift? Try Alamofire for a more conventional set of APIs.

Tận dụng sức trẻ của ngôn ngữ mới nên Alamofire có cách viết bóng bẩy, hiện đại hơn so với đàn anh AFNetworking. Vậy nên mình cũng tạo mới một lớp wrap cho Alamofire để sử dụng chung giữa các project của mình.

2. Hướng đi

Có lẽ hầu hết ai cũng wrap lại AFNetworking bằng cách viết 1 lớp APIClient sử dụng singleton với hàm init

- (instancetype)initWithBaseURL:(NSURL *)url

Ngay từ hàm init ta có thể tạo baseURL cho toàn bộ những API của mình. Cách này khá tiện dụng khi project chỉ sử dụng duy nhất một base url. Tuy nhiên project mới đây của mình có tận 3 base url. Thế nên việc sử dụng singleton với base url có vẻ không hợp lý lắm. Có lẽ chính vì thế ở Alamofire ta không thấy có hàm tương tự nữa.

Trên đường tìm kiếm hướng đi mới, mình thấy một cách khá là hot trend: sử dụng router. Alamofire cung cấp cho ta protocol URLRequestConvertible phục vụ mục đích đấy

Lấy luôn một ví dụ triển khai URLRequestConvertible

enum Router: URLRequestConvertible {
    case search(query: String, page: Int)

    static let baseURLString = "https://example.com"
    static let perPage = 50

    // MARK: URLRequestConvertible

    func asURLRequest() throws -> URLRequest {
        let result: (path: String, parameters: Parameters) = {
            switch self {
            case let .search(query, page) where page > 0:
                return ("/search", ["q": query, "offset": Router.perPage * page])
            case let .search(query, _):
                return ("/search", ["q": query])
            }
        }()

        let url = try Router.baseURLString.asURL()
        let urlRequest = URLRequest(url: url.appendingPathComponent(result.path))

        return try URLEncoding.default.encode(urlRequest, with: result.parameters)
    }
}

Ở đây ta cần phải đưa ra các thông tin để tạo ra request: base url, path, param. Đây là các trường cơ bản nhất, tuy nhiên ở một popular API ta còn cần biết những thông tin khác như method (get, set, put, delete), token,...Nếu ta triển khai hết trong 1 class implement URLRequestConvertible thì viết khá là dài dòng so với cách tạo một BASE_URL duy nhất. Đây là điều mà mình không muốn :joy:.

3. Triển khai

Lấy ý tưởng từ chris.eidhof, mình viết theo hướng tạo ra 1 router bao gồm các thông tin thiết yếu của 1 API: url, method, token (không đưa thông tin params vào trong router)

public protocol ONPath {
    var path : String { get }
}

public protocol ONToken {

    /// example: "Bearer", "Basic", etc
    var tokenKind : String { get }
    var tokenStr : String { get }
    var isAuthorization : Bool { get }
}

public protocol ONMethod {
    var method : Alamofire.HTTPMethod { get }
}

// protocol container
public protocol ONUrl : ONPath, ONToken, ONMethod {
    var baseURL : String { get }
    var url : String { get }
}

Khá là đơn giản. Bây giờ ta sẽ định nghĩa 1 router dựa trên protocol trên

// MARK: - router for github
enum ONGithubURL : ONUrl {
    var baseURL: String {
        return BASE_URL
    }

    case getGitHubUser(account : String)

    var path: String {
        switch self {
        case .getGitHubUser(let acc):
            return "users/\(acc)"
        }
    }
}

// MARK: - method for github urls
extension ONGithubURL : ONMethod {
    var method: HTTPMethod {
        switch self {
        case .getGitHubUser(_):
            return .get
        }
    }
}

// MARK: - generate token for github url
extension ONGithubURL : ONToken {
    var isAuthorization: Bool {
        switch self {
        case .getGitHubUser(_):
            return false
        }
    }

    var tokenStr : String {
        switch self {
        case .getGitHubUser(_):
            return ""
        }
    }
}

Từ enum ONGithubURL, ta có thể tạo ra router như sau

let router = ONGithubURL.getGitHubUser(account: "gg4acrossover")
router.url // https://api.github.com/users/gg4acrossover
router.method // GET

Với cách sử dụng router như trên ta có thể tạo ra url theo ý muốn. Từ đó ta có thể tạo hàm call API sử dụng Alamofire theo cách sau

public func call(router: ONUrl, params: [String: Any]? = nil, success: @escaping responseJSON, fail: @escaping responseError) -> DataRequest {

    // add accept header
    var headers = ["Accept" : "application/json,charset=utf-8,text/html"]

    // add authorization if need
    if router.isAuthorization {
        headers["Authorization"] = router.tokenKind + " " + router.tokenStr
    }

    debugPrint(router.method.rawValue + " " + router.url)
    debugPrint("Headers: \(headers)")

    return sessionMng.request(router.url, method: router.method, parameters: params, headers: headers)
                     .validate()
                     .responseJSON { response in
            switch response.result {
            case .success(let value):
                let json = JSON(value)
                success(json)
            case .failure(let error):
                if error._code == NSURLErrorTimedOut {
                debugPrint("Request Timeout...")
                }
                fail(error)
        }
    }
}

Ví dụ sử dụng ONGithubURL enum

let router = ONGithubURL.getGitHubUser(account: "gg4acrossover")
ONAPIClient.default.call(router: router, success: success, fail: fail)

Code tham khảo: source

Các bài viết có thể tham khảo thêm:

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

VietHQ

10 bài viết.
5 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
5 1
Thời gian đầu làm việc với objc mình khá băn khoăn trong việc sử dụng các thuộc tính trong property như strong, weak, copy, assign. Nhân lúc rảnh r...
VietHQ viết 3 năm trước
5 1
White
2 2
1.Tản mạn Cách đơn giản nhất để giảm bớt bug là viết code ít đi. Chân lý đó đã được đưa vào một định luật nổi tiếng, hồi phổ thông ai cũng từng ki...
VietHQ viết 1 năm trước
2 2
White
2 0
Có nhiều cách viết blog công nghệ hơn là làm bánh hay làm tình. Những ngày này Hà Nội mưa liên miên, được cái mát giời, mình lại tức cảnh sinh tìn...
VietHQ viết 11 tháng trước
2 0
Bài viết liên quan
White
11 4
(Link) (Link) (Link) Ở 2 phần tut trước, mình đã hướng dẫn khá chi tiết cách viết một ứng dụng camera có tích hợp chức năng nhận diện khuôn mặ...
HoangPH viết 3 năm trước
11 4
Male avatar
0 0
RxSwift: Bài 6: RxCocoa (Part 4) Units 1. Khái niệm chung RxCocoa cung cấp nhiều tính năng mới để làm việc với Cocoa và UIKit 1 cách dễ dàng hơn....
Bùi Khánh Duy viết 4 tháng trước
0 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
10 bài viết.
5 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!