Nếu bạn đang đọc bài viết này, rất có thể bạn cần triển khai một số lệnh gọi REST trong ứng dụng của mình. Trừ khi bạn là người yêu thích DIY (Do It Yourself), nếu không, bạn có thể đã sử dụng thư viện mạng phổ biến nhất là Alamofire. Tôi cũng đã làm điều đó.

Nhưng hôm nay chúng ta sẽ thử một cái gì đó khác, một cái gì đó mà tôi thấy có tính module hơn và hài lòng hơn khi sử dụng. 

Moya không thực sự là một lớp mạng, nhưng nó thực sự là… một trình bao bọc Alamofire. Bởi vì Alamofire khá tốt, nhưng làm thế nào dễ dàng tìm thấy chính mình với một NetworkManager.swift khổng lồ?

Moya đến để giải cứu, được thiết kế để khuyến khích bạn tách định nghĩa service của mình ở một nơi khác với việc triển khai thực tế. 

Tuy nhiên, chúng tôi không dừng lại ở đây, bởi vì chúng tôi sẽ làm tất cả những điều này với RxSwift.

Chúng tôi sẽ giữ điều này cơ bản và sử dụng một số fake API làm điểm cuối và chúng tôi sẽ GET và DELETE một số bài đăng.

 

 

Cùng bắt đầu nhé!

Trước hết, chúng ta cần cài đặt Moya + RxSwift:

 

pod 'Moya/RxSwift', '~> 12.0'

 

Thêm thư viện Moya trong Podfile và thực hiện pod install. Nếu không sử dụng Cocoapods, bạn có thể tìm các phương pháp cài đặt khác trên README của repo tại đây.

Chúng ta sẽ bắt đầu bằng cách xác định các điểm cuối mà chúng ta sẽ sử dụng.

Để làm như vậy, chúng ta cần một enum với một case cho mỗi hoạt động. Nếu chúng ta cần chuyển các tham số cho cuộc gọi của mình, chúng ta có thể chuyển chúng vào case trong enum.

 

enum ForumService {
    case getPosts
    case deletePost(id: Int)
}

 

Hãy hình thành dịch vụ của chúng tôi bằng cách triển khai Moya’s TargetType.

Giao thức này sẽ yêu cầu chúng ta xác định cách Moya xây dựng mỗi cuộc gọi.

 

import Moya

extension ForumService: TargetType {
	
    // This is the base URL we'll be using, typically our server.
    var baseURL: URL {
	return URL(string: "https://jsonplaceholder.typicode.com")!
    }

    // This is the path of each operation that will be appended to our base URL.
    var path: String {
        switch self {
        case .getPosts:
            return "/posts"
        case .deletePost(let id):
            return "/posts/\(id)"
        }
    }

    // Here we specify which method our calls should use.
    var method: Method {
        switch self {
        case .getPosts:
            return .get
        case .deletePost:
            return .delete
        }
    }

    // Here we specify body parameters, objects, files etc. 
    // or just do a plain request without a body.
    // In this example we will not pass anything in the body of the request.
    var task: Task {
        return .requestPlain
    }

    // These are the headers that our service requires. 
    // Usually you would pass auth tokens here.
    var headers: [String: String]? {
        return ["Content-type": "application/json"]
    }

    // This is sample return data that you can use to mock and test your services,
    // but we won't be covering this.
    var sampleData: Data {
        return Data()
    }

}

 

Được rồi, chúng tôi đã hoàn tất các thông số dịch vụ của mình và bây giờ chúng tôi chỉ cần triển khai các cuộc gọi. Bây giờ đến lượt RxSwift.

 

import RxSwift
import Moya

struct ForumNetworkManager {

    // I'm using a singleton for the sake of demonstration and other lies I tell myself
    private static let shared = ForumNetworkManager()
    
    // This is the provider for the service we defined earlier
    private let provider = MoyaProvider<ForumService>()
    
    private init() {}
    
    // We're returning a Single response with just an array with the retrieved posts.
    // You could return an Observable<PostJSON> if you need to, this is just an example.
    func getPosts() -> Single<[PostJSON]> {
        return provider.rx                              // we use the Reactive component for our provider
            .request(.getPosts)                         // we specify the call 
            .filterSuccessfulStatusAndRedirectCodes()   // we tell it to only complete the call if the operation is successful, otherwise it will give us an error
            .map([PostJSON].self)                       // we map the response to our Codable objects
    }
    
    // Here we return a Completable because we only need to know if the call is done or if there was an error.
    func deletePost(with id: Int) -> Completable {
        return provider.rx
            .request(.deletePost(id: id))    
            .filterSuccessfulStatusAndRedirectCodes()
            .asObservable().ignoreElements()            // we're converting to Observable and ignoring events in order to return a Completable, which skips onNext and only maps to onCompleted
    }
    
}

 

Chúng ta cần thực sự sử dụng các cuộc gọi đúng không? Chúng tôi sẽ làm điều đó ngay bây giờ.

Đối với ví dụ này, chúng tôi sẽ trả về một Bảng hoàn thành, để đơn giản và trong onSuccess, chúng tôi quản lý dữ liệu được phân tích cú pháp từ phản hồi của máy chủ.

 

import RxSwift

class PostsViewModel {
    
    func fetchRemotePosts() -> Completable {
        return .create { observer in
            ForumNetworkManager.getPosts()
                .subscribe(onSuccess: { jsonPosts: [PostJSON] in
                    // we fetched the posts
                    observer(.completed)
                }, onError: { error in
                    // there was an error fetching the posts
                    observer(.error(error))
                })
        }
    }

    func deletePost(with id: Int) -> Completable {
        return .create { observer in
            ForumNetworkManager.deletePost(with: id)
                .subscribe(onCompleted: {
                    // we successfully deleted the post
                    observer(.completed)
                }, onError: { error in 
                    // there was an error deleting the post
                    observer(.error(error))
                })
        }
    }
	
}

 

Chúng tôi đã hoàn thành phần mạng của mình và bây giờ bạn chỉ cần xử lý kết quả của các cuộc gọi.

Nó thế nào? Việc thiết lập lớp mạng của bạn với Moya hơi dài dòng hơn một chút so với Alamofire nhưng đầu tư thêm một chút thời gian sẽ mang lại hiệu quả lâu dài và thêm một số RxSwift chỉ làm cho nó tốt hơn.

 

Hãy thêm gia vị cho lớp mạng của chúng ta.

Chúng tôi sắp thêm hai thứ yêu thích của mọi nhà phát triển: logging và error handling (ghi nhật ký và xử lý lỗi).

Logging

Đây là điều tuyệt vời nhất từ ​​trước đến nay và cũng là điều dễ dàng nhất vì Moya đi kèm với trình ghi nhật ký tích hợp.

Ghi nhật ký được xử lý bởi một plugin, có thể được thiết lập như sau:

 

MoyaProvider<ForumService>(plugins: [NetworkLoggerPlugin(verbose: true)])

 

Sau đó, bạn sẽ bắt đầu thấy các cuộc gọi được đăng nhập trong bảng điều khiển. Bạn cũng có thể tạo plugin của riêng mình!

Error handling

Cuối cùng nhưng không kém phần quan trọng, xử lý lỗi. Bạn biết cách xử lý lỗi của mình nên tôi sẽ không dạy bạn điều đó, nhưng vì bạn có thể sử dụng các enum tùy chỉnh để biểu diễn lỗi, bạn phải tìm ra những gì đã xảy ra trong cuộc gọi và trả lại thông tin thích hợp để cho người dùng biết điều gì đã xảy ra.

Hãy xem cách chúng tôi có thể xử lý lỗi trong lớp mạng của mình:

 

enum ExampleError: Error {
    case somethingHappened
}

static func getPosts() -> Single<[PostJSON]> {
    return provider.rx                              
        .request(.getPosts)                         
        .filterSuccessfulStatusAndRedirectCodes()   
        .map([PostJSON].self)                       
        .catchError { error in 
            // this function catches any error that happens, 
            // you can recover and continue the sequence with another observable, 
            // but we're not doing this right now
                     
            // todo parse error and figure out what happened
            throw ExampleError.somethingHappened
        }
}

 

Khi xử lý lỗi, bạn sẽ chỉ cần chuyển lỗi bạn nhận được sang (các) lỗi bạn mong đợi.

Kết luận

Tôi hy vọng bạn thích giải pháp khác nhau này. Một số nhà phát triển chống lại việc thêm một lớp khác vào ngăn xếp mạng của họ, nhưng tôi thấy đây là một sự thỏa hiệp tốt giữa việc có một trình quản lý mạng lộn xộn và việc phải kiến trúc, kiểm tra và duy trì lớp của riêng bạn.
Hãy cho tôi biết nếu bạn đã thử điều này trong ứng dụng của mình và nếu bạn đã triển khai nó theo một cách khác!

 

Techmaster liên tục mở các lớp đào tạo lập trình di động iOS Swift

Nguồn bài viết tại đây.