优化 Swift API GET 函数

Refining Swift API GET Functions

我正在做一个练习项目,其中 iOS 应用程序打印来自 jsonplaceholder.typicode.com 的 /post 列表,当用户选择一个时,详细视图控制器是已加载并显示有关 post 的更多信息(作者和评论数)。

我为三个不同的端点发出了三个单独的 GET 请求,因为每个端点都需要不同的 return 类型和不同的参数(或者根本 none)。

我想尽可能多地使用三者之间的共同代码,并将其放入一个新函数中以整理 class,但我觉得我可以做更多的事情。

有没有办法让这些结构的 return 类型更通用,用一个 Switch 来确定将 json 响应映射到哪个?任何指导将不胜感激。

import UIKit

struct Post: Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

struct Author: Codable {
    let name: String
}

struct Comment: Codable {
    let postId: Int
    let id: Int
    let name: String
    let email: String
    let body: String
}

enum Result<Value> {
    case success(Value)
    case failure(Error)
}

class APIManager {

static let sharedInstance = APIManager()

func getUrl(for path: String) -> URL {
    var urlComponents = URLComponents()
    urlComponents.scheme = "https"
    urlComponents.host = "jsonplaceholder.typicode.com"
    urlComponents.path = path

    guard let url = urlComponents.url else { fatalError("Could not create URL from components") }

    return url
}

func getPosts(completion: ((Result<[Post]>) -> Void)?) {
    let url = getUrl(for: "/posts")

    var request = URLRequest(url: url)
    request.httpMethod = "GET"

    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)
    let task = session.dataTask(with: request) { (responseData, response, responseError) in
        DispatchQueue.main.async {
            if let error = responseError {
                completion?(.failure(error))
            } else if let jsonData = responseData {
                let decoder = JSONDecoder()

                do {
                    let posts = try decoder.decode([Post].self, from: jsonData)
                    completion?(.success(posts))
                } catch {
                    completion?(.failure(error))
                }
            } else {
                let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
                completion?(.failure(error))
            }
        }
    }

    task.resume()
}

func getAuthor(for userId: Int, completion: ((Result<String>) -> Void)?) {
    let url = getUrl(for: "/users/\(userId)")

    var request = URLRequest(url: url)
    request.httpMethod = "GET"

    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)
    let task = session.dataTask(with: request) { (responseData, response, responseError) in
        DispatchQueue.main.async {
            if let error = responseError {
                completion?(.failure(error))
            } else if let jsonData = responseData {
                let decoder = JSONDecoder()

                do {
                    let author = try decoder.decode(Author.self, from: jsonData)
                    completion?(.success(author.name))
                } catch {
                    completion?(.failure(error))
                }
            } else {
                let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
                completion?(.failure(error))
            }
        }
    }

    task.resume()
}

func getComments(for postId: Int, completion: ((Result<[Comment]>) -> Void)?) {
    let url = getUrl(for: "/posts/\(postId)/comments")

    var request = URLRequest(url: url)
    request.httpMethod = "GET"

    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)
    let task = session.dataTask(with: request) { (responseData, response, responseError) in
        DispatchQueue.main.async {
            if let error = responseError {
                completion?(.failure(error))
            } else if let jsonData = responseData {
                let decoder = JSONDecoder()

                do {
                    let comments = try decoder.decode([Comment].self, from: jsonData)
                    completion?(.success(comments))
                } catch {
                    completion?(.failure(error))
                }
            } else {
                let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
                completion?(.failure(error))
            }
        }
    }

    task.resume()
  }
}

只需利用通用 Result 类型:

class APIManager {

    static let sharedInstance = APIManager()

    private func getUrl(for path: String) -> URL {
        var urlComponents = URLComponents()
        urlComponents.scheme = "https"
        urlComponents.host = "jsonplaceholder.typicode.com"
        urlComponents.path = path

        guard let url = urlComponents.url else { fatalError("Could not create URL from components") }

        return url
    }

    private func postsURL() -> URL { return getUrl(for: "/posts") }
    private func usersURL(for userId : Int) -> URL { return getUrl(for: "/users/\(userId)") }
    private func commentsURL(for postId : Int) -> URL { return getUrl(for: "/posts/\(postId)/comments") }

    func getPosts(completion: @escaping (Result<[Post]>) -> Void) {
        getInfo(for: postsURL(), completion: completion)
    }

    func getAuthor(for userId: Int, completion: @escaping (Result<Author>) -> Void) {
        getInfo(for: usersURL(for: userId), completion: completion)
    }

    func getComments(for postId: Int, completion: @escaping (Result<[Comment]>) -> Void) {
        getInfo(for: commentsURL(for: postId), completion: completion)
    }

    private func getInfo<T: Decodable>(for url : URL, completion: @escaping (Result<T>) -> Void) {
        let task = URLSession.shared.dataTask(with: url) { (data, _, error) in
            DispatchQueue.main.async {
                if let error = error {
                    completion(.failure(error))
                } else {
                    let decoder = JSONDecoder()
                    do {
                        let comments = try decoder.decode(T.self, from: data!)
                        completion(.success(comments))
                    } catch {
                        completion(.failure(error))
                    }
                }
            }
        }
        task.resume()
    }
}

并使用它

let manager = APIManager.sharedInstance
manager.getAuthor(for: 1) { result in
    switch result {
    case .success(let author) : print(author.name)
    case .failure(let error) : print(error)
    }
}