0

Viết lớp mạng trong Swift: Protocol-Oriented Approach ( Phần 2 )

Link bài viết cũ: Phần 1

Build Request

Tạo function private bên trong Router đặt tên buildRequest. Function này chịu trách nhiệm với tất cả phần việc quan trọng trong lớp network của chúng ta. Bản chất là chuyển đổi EndPointType thành URLRequest. Mỗi EndPoint trở thành 1 requset chúng ta có thể chuyền nó vào phiên. Phần lớn sẽ hoàn thành ở đây vì vậy chúng ta sẽ xem xét chúng mỗi phương thức 1 cách riêng biệt. Cùng phân tích buildRequest method:

  1. Chúng ta khởi tạo 1 biesn request thuộc kiểu URLRequest. Cung cấp nó cho base URL của chúng ta và thêm path cụ thể mà chúng ta sẽ sử dụng.
  2. Cấu hình httpMethod của request phù hợp với EndPoint của chúng ta.
  3. Chúng ta tạo 1 khối do-try-catch kể từ khi bộ mã hóa của chúng ta ném 1 lỗi. Bằng việc tạo 1 khối do-try-catch lớn chúng ta không cần phải tạo mỗi khối tách rời cho mỗi lần try
  4. Chuyển đổi trên route.task
  5. Tùy vào mỗi task, gọi mã hóa thích hợp

Cấu hình thông số ( Configure Parameters)

Tạo 1 function đặt tên configureParameters bbên trong Router

Function này là mấu chốt cho mã hóa thông số của chúng ta. Từ khi API của chúng ta kì vọng tất cả bodyParameters như là JSON và URLParameters được mã hóa URL chúng ta chỉ việc truyền vào thông số tùy chọn vào bên trong bộ mã hóa được chỉ định. Nếu bạn đang làm việc với 1 API mà có đa dạng phong cách mã hóa tôi gợi ý sửa đổi HTTPTask để lấy bộ mã hóa Enum. Enum này nên có tất cả các cấu trúc của bộ mã hóa bạn cần. Sau đó bên trong configureParameters thêm các đối số bổ sung cho bộ mã hóa Enum của bạn. Tương thích enum và đối số phù hợp.

Thêm Headders bổ sung ( Add Additional Headers)

Tạo function tên addAdditionalHeaders bên trong Router.

Đơn giản hteem tất cả phần header bổ sung vào thành phần của request headers

Hủy bỏ

Hủy bỏ function đang thực hiện bằng như bên dưới:

Thực hành

Giờ cùng sử dụng lớp mạng chúng ta đã xây dựng ở phần ví dụ thực tế. Chúng ta sẽ sử dụng TheMovieDB🍿 để lấy thông tin phim cho ứng dụng của chúng ta.

MovieEndPoint

MovieEndPoint khá giống với Targer Type chúng ta có trong Bắt đầu với Moya (xem nó nếu bạn chưa từng đọc nó lúc tước). Tahy vì sử dụng Moya'sTargetType chúng ta giờ chỉ việc sử dụng EndPointType của chúng ta. Đặt file này bên trong Group EndPoint.

import Foundation


enum NetworkEnvironment {
    case qa
    case production
    case staging
}

public enum MovieApi {
    case recommended(id:Int)
    case popular(page:Int)
    case newMovies(page:Int)
    case video(id:Int)
}

extension MovieApi: EndPointType {
    
    var environmentBaseURL : String {
        switch NetworkManager.environment {
        case .production: return "https://api.themoviedb.org/3/movie/"
        case .qa: return "https://qa.themoviedb.org/3/movie/"
        case .staging: return "https://staging.themoviedb.org/3/movie/"
        }
    }
    
    var baseURL: URL {
        guard let url = URL(string: environmentBaseURL) else { fatalError("baseURL could not be configured.")}
        return url
    }
    
    var path: String {
        switch self {
        case .recommended(let id):
            return "\(id)/recommendations"
        case .popular:
            return "popular"
        case .newMovies:
            return "now_playing"
        case .video(let id):
            return "\(id)/videos"
        }
    }
    
    var httpMethod: HTTPMethod {
        return .get
    }
    
    var task: HTTPTask {
        switch self {
        case .newMovies(let page):
            return .requestParameters(bodyParameters: nil,
                                      urlParameters: ["page":page,
                                                      "api_key":NetworkManager.MovieAPIKey])
        default:
            return .request
        }
    }
    
    var headers: HTTPHeaders? {
        return nil
    }
}

MovieModel

MovieModel của chúng ta cũng không thay nổi vì response của TheMovieDB vẫn giống JSON. Chúng ta sử dụng giao thức Decodable để chuyển đổi JSON của chúng ta thành model của mình. Đặt file này bên trong Model group

import Foundation

struct MovieApiResponse {
    let page: Int
    let numberOfResults: Int
    let numberOfPages: Int
    let movies: [Movie]
}

extension MovieApiResponse: Decodable {
    
    private enum MovieApiResponseCodingKeys: String, CodingKey {
        case page
        case numberOfResults = "total_results"
        case numberOfPages = "total_pages"
        case movies = "results"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: MovieApiResponseCodingKeys.self)
        
        page = try container.decode(Int.self, forKey: .page)
        numberOfResults = try container.decode(Int.self, forKey: .numberOfResults)
        numberOfPages = try container.decode(Int.self, forKey: .numberOfPages)
        movies = try container.decode([Movie].self, forKey: .movies)
        
    }
}


struct Movie {
    let id: Int
    let posterPath: String
    let backdrop: String
    let title: String
    let releaseDate: String
    let rating: Double
    let overview: String
}

extension Movie: Decodable {
    
    enum MovieCodingKeys: String, CodingKey {
        case id
        case posterPath = "poster_path"
        case backdrop = "backdrop_path"
        case title
        case releaseDate = "release_date"
        case rating = "vote_average"
        case overview
    }
    
    
    init(from decoder: Decoder) throws {
        let movieContainer = try decoder.container(keyedBy: MovieCodingKeys.self)
        
        id = try movieContainer.decode(Int.self, forKey: .id)
        posterPath = try movieContainer.decode(String.self, forKey: .posterPath)
        backdrop = try movieContainer.decode(String.self, forKey: .backdrop)
        title = try movieContainer.decode(String.self, forKey: .title)
        releaseDate = try movieContainer.decode(String.self, forKey: .releaseDate)
        rating = try movieContainer.decode(Double.self, forKey: .rating)
        overview = try movieContainer.decode(String.self, forKey: .overview)
    }
}

Quản lý mạng (NetworkManager)

Tạo file tên NetworkManager và đặt nó bên trong group Manager . Từ bây giờ NetworkManager của chúng ta sẽ chỉ 2 thành phần không đổi: API key của bạn và môi trường network (Reference MovieEndPoint). NetworkManager cũng có Router thuộc 1 loại của MovieApi.

Network Response

Tạo 1 Enum đặt tên NetworkResponse bên trong NetworkManager.

Chúng ta sẽ sử dụng enum để xử lý phàn hồi từ API và thể hiện 1 thông báo thích ợp

Kết quả

Tạo 1 Enum Result bên trong NetworkManager.

Kết quả Enum rất hữu ích và có thể được sử dụng cho nhiều thứ. Chúng ta sẽ sử dụng kết quả để quyết định cho dù việc bạn gọi API thành công hay thất bại. Nếu nó thất bại chúng ta sẽ trả về 1 tin nhắn lỗi cùng với lí do. Thêm nữa lập trình Result Oriented bạn có thể xem hoặc đọc bài nói truyện này

Xử lý phản ứng mạng ( Handle Network Responses )

Tạo 1 hàm tên handleNetworkResponse. Hàm này sẽ yêu cầu đối số là HTTPResponse và sẽ trả về Result< String >.

Ở đây chúng ta chuyển đổi ở HTTPResponse's statusCode. statusCode là 1 giao thức HTTP sẽ nói cho chúng ta trạng thái của phản ứng. Về cơ bản bất cứ thứ gù khoản 200 và 299 là thành công. Tìm hiểu thêm statusCodes đọc tại [đây] (https://www.restapitutorial.com/httpstatuscodes.html)

Thực hiện gọi

Giờ thì chúng ta đã đặt nền móng cơ bản vững chắc cho lớp mạng của mình. Đây là lúc chúng ta thực hiện gọi!

Chúng ta sẽ đem đến 1 danh sách những bộ phim mới từ API. Tạo hàm tên getNewMovies.

Hãy cùng phân tích bước từng bước của hàm này:

  1. Chugns ta định nghĩa phương thức getNewMovies với 2 đối số: 1 số trang và 1 completion nó sẽ trả lại mảng optional Movie hoặc tin nhắn optional lỗi.
  2. Chúng ta gọi Router của chúng ta. Truyền vào số trang và xử lý completion bên trong 1 closure.
  3. 1 URLSession sẽ trả về lỗi nếu không có mạng hoặc gọi tới API không tạo thành bởi 1 vài lí do. Hãy ghi lại điều này, sẽ không phải a API thất bại. Thất bại này là do phía khách hàng và có thể là do đường truyền mạng yếu
  4. Chúng ta cần đưa ra response của chúng ta tới 1 HTTPURLResponse bởi chúng ta cần truy xuất thuộc tính statusCode.
  5. Chúng ta xác nhận 1 result cái chúng ta lấy được từ phương thức handleNetworkResponse . Chúng ta tiếp theo xem xét kết quả trong khối switch-case.
  6. Success nghĩa là chúng ta có thể để giao tiếp với API thành công và lấy lại được phản hồi thích hợp. Tiếp theo chúng ta kiểm tra phản hồi lại cùng với dữ liệu. Và nếu quá hạn chúng ta chỉ đơn giản kết thúc phương thức và gọi lại.
  7. Nếu phản hồi trả về cùng với dữ liệu, chúng ta giải mã dữ liệu trở thành mô hình của chúng ta. Sau đó chúng ta giải mã phim tới completion.
  8. Trong trường hợp failure chúng ta đơn giản truyền error tới completion.

Và xong, đấy chính là lớp mạng của chúng ta bằng chỉ Swift với không Cocoapods hay thư viện thứ 3 nào. Để kiểm tra api request bằng cách lấy phim tạo viewController với 1 Network Manager tiếp theo gọi getNewMovies trong manager.

class MainViewController: UIViewController {
    
    var networkManager: NetworkManager!
    
    init(networkManager: NetworkManager) {
        super.init(nibName: nil, bundle: nil)
        self.networkManager = networkManager
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .green
        networkManager.getNewMovies(page: 1) { movies, error in
            if let error = error {
                print(error)
            }
            if let movies = movies {
                print(movies)
            }
        }
    }
}

DETOUR- NETWORK LOGGER

1 trong những tính năng mà tôi yêu thích của Moya là network logger. Nó đem lại cho nó 1 cách đơn giản để debug và nhìn xem cái gì đang diễn ra với yêu cầu và phản hồi bởi logging tât cả lưu lượng mạng. Điều này được định nghĩa bởi tính năng mà tôi muốn khi tôi quyết định sử dụng lớp mạng này. Tạo 1 file tên NetworkLogger và đặt nó bên trong nhóm Service. Tôi sử dụng code để log yêu cầu tới bảng hiển thị. Tôi không muốn lộ ra nơi mà chúng ta đặt đoạn code này bên trong lớp mạng của chúng tôi. Như một thử thách để bạn tìm hiểu và tạo 1 hàm để log phản hồi tới bảng hiển thị và cũng tìm ra cách đặt bên trong cấu trúc của chúng tôi sắp xếp hàm gọi. [Place Gist file]

Thông tin bổ sung: static func log(response: URLResponse) {}

Bổ sung

Có bao giờ bạn tìm thấy bên trong Xcode với 1 đoạn placeholder mà bạn không thực sự hiểu chúng? Đơn giản với ví dụ dưới, chúng ta chỉ việc sử dụng Router của mình.

NetwrokRouterComletion là thứ chúng ta sử dụng. Bất cứ khi nào chúng ta dùng nó, thật khó để nhớ chính xác kiểu là gì và cách chúng nào chúng ta nên dùng nó. May thay Xocde đã giải cứu cho chúng ta! Chỉ việc double click vào placeholder và Xcode sẽ làm phần việc còn lại

Kết luận

Giờ chúng ta đã có thể sử dụng dễ dàng giao thức lớp mạng định hướng có thể tùy chỉnh. Chúng ta có thể kiểm soát hoàn toàn chức năng và hiểu được cách nó hoạt động. Với việc bắt tay thực hành bài tập tôi thành thật nói rằng bản thân tôi đã học được những điều mới mẻ. Nên tôi rất tự hào với công sức nhỏ này hơn là chỉ việc đặt thư viện và dùng. Hi vọng, bài viết này cung cấp thông tin không quá khó để bạn tạo được lớp mạng của riêng mình bằng Swift.😛 Chỉ đừng làm điều này:

Bạn có thể tìm thấy Source code ở gitHub của tôi. Cám ơn vì đã đọc!

Bài viết gốc: https://medium.com/flawless-app-stories/writing-network-layer-in-swift-protocol-oriented-approach-4fa40ef1f908


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í