Swift 面向协议编程–实践

如何将网络请求封装得更容易扩展

import Foundation

struct User {
    let name: String
    let message: String
    
    /// 将网络请求的数据,解析为json对象,然后初始化User实例
    init?(data: Data) {
        guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else {
            return nil
        }

        guard let name = obj["name"] as? String else {
            return nil
        }
        
        guard let message = obj["message"] as? String else {
            return nil
        }
        
        self.name = name
        self.message = message
    }
}


/// 定义请求方式
enum HTTPMethod: String {
    case GET
    case POST
}

/// 定义请求类
protocol Request {
    var host: String {get}
    var path: String {get}
    
    var method: HTTPMethod {get}
    var parameter: [String: Any] {get}
    
    ///为了所有的Request都通用,所以将User具体的返回类型,设置为关联类型
    associatedtype Response
    
    /// 将请求数据解析的功能下沉
    func parse(data: Data) -> Response?
}

/// 为了任意的请求都可以通过同样的方法发送,所以定义在request的扩展上
extension Request {
    func send(handler:@escaping (Response?) -> Void) {
        /// 发起网络请求
        let url = URL(string: host.appending(path))!
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        
        /// 这是尾随必闭包的写法
        let task = URLSession.shared.dataTask(with: request) {
            data,res,error in
            
            if let data = data, let res = parse(data: data) {
                DispatchQueue.main.async { handler(res) }
            }else {
                DispatchQueue.main.async { handler(nil) }
            }
        }
        
        task.resume()
    }
}

/// 定义用户请求的实现类
struct UserRequest: Request {
    /// 这里是为了确定协议Request中,关联的类型
    typealias Response = User
    
    let name: String
    
    let host = "https://api.onevcat.com"
    var path: String {
        return "/user/\(name)"
    }
    let method: HTTPMethod = .GET
    let parameter: [String : Any] = [:]
    
    
    func parse(data: Data) -> User? {
        return User(data: data)
    }
}


/// 调用请求
class HomeViewController: ViewController {
    override func viewDidLoad() {
        let request = UserRequest(name: "onecat")
        request.send { (user) in
            if let user = user {
                print(user.name,user.message)
            }
        }
    }
}
复制代码

以上的结构,最大的问题是Request集成了太多的功能,不仅定义了host的值,还包括了如何解析数据,而且网络请求的方法send也是绑定在一起,这是不合理的,它应该只关心请求的入口和期望的响应类型

升级改造后

  1. 将发送网络请求独立与网络请求类
  2. 将数据解析在数据模型Response中处理

struct User: Decodable {
    let name: String
    let message: String
    
    /// 将网络请求的数据,解析为json对象,然后初始化User实例
    init?(data: Data) {
        guard let obj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else {
            return nil
        }

        guard let name = obj["name"] as? String else {
            return nil
        }
        
        guard let message = obj["message"] as? String else {
            return nil
        }
        
        self.name = name
        self.message = message
    }
    
    
    /// 定义数据解析
    static func parse(data: Data) -> User? {
        return User(data: data)
    }
    
}


/// 定义请求方式
enum HTTPMethod: String {
    case GET
    case POST
}


/// 将发送网络请求 独立出来
protocol Client {
    /**
         protocol Client {
             func send(_ r: Request, handler: @escaping (Request.Response?) -> Void)
         }
        这种写法编译器会报错,是由于Request内包含关联类型的协议,所以不能作为独立的类型来是使用
     
        只能够将它作为类型约束,来限制输入参数request,以下才是正确的写法
     
     */
    func send<T: Request>(_ r: T, handle: @escaping(T.Response?) -> Void)
    
    var host: String { get }
}



/// 实现网络请求
struct URLSessionClient: Client {
    let host = "https://api.onevcat.com"
    
    func send<T>(_ r: T, handle: @escaping (T.Response?) -> Void) where T : Request {
        let url = URL(string: host.appending(r.path))!
        var request = URLRequest(url: url)
        request.httpMethod = r.method.rawValue
        
        let task = URLSession.shared.dataTask(with: request) {
            data , _ , error in
            if let data = data , let res = T.Response.parse(data: data) {
                DispatchQueue.main.async { handle(res) }
            }else{
                DispatchQueue.main.async { handle(nil) }
            }
        }
        
        task.resume()
        
    }
}



/// 数据解析:
/// 将发送请求的部分和本事分离开了,请求不需要知道如何解析得到的数据
/// 交给Response完成
protocol Decodable {
    static func parse(data: Data) -> Self?
}



/// 定义请求类
protocol Request {
    var path: String { get }
    var method: HTTPMethod { get }
    var parameter: [String: Any] { get }
    
    /// 为了所有的Request都通用,所以将User具体的返回类型,设置为关联类型
    /// 需要在Request的Response关联类型中为它加上这个限制,可以保证所有的Response都可以对数据进行解析
    associatedtype Response: Decodable
}


struct UserRequest: Request {
    typealias Response = User

    let name: String
    var path: String {
        return "/user/\(name)"
    }
    let method: HTTPMethod = .GET
    let parameter: [String : Any] = [:]
}

/// 请求调用方式
class HomeViewController: ViewController {
    override func viewDidLoad() {
        URLSessionClient().send(UserRequest(name: "onevcat")) { user in
            if let user = user {
                print("\(user.message) from \(user.name)")
            }
        }
    }
}

复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享