美文网首页
一个可以快速开发的代码封装框架--PJQuicklyDev2

一个可以快速开发的代码封装框架--PJQuicklyDev2

作者: 飘金 | 来源:发表于2018-08-05 16:10 被阅读0次

    PJQuicklyDev2 快速开发框架的2.0版本,Swift4.0 Xcode9.1

    1.0版本如果有兴趣请移步,相关信息可以参考1.0版本。

    PJQuicklyDev2在GitHub上的地址

    在看了🐱神的文章面向协议编程与 Cocoa 的邂逅 (上)面向协议编程与 Cocoa 的邂逅 (下)感觉1.0版本的网络与数据解析耦合度太高了,不方便扩展。想要把🐱神的面向协议的思想应用起来,恰巧最近开始看Swift4.0于是就有了快速开发框架2.0版本。主要优化的底层点为网络请求与数据解析。还有tableView网络请求加载数据。

    网络请求

    本次的网络请求重构的目的在于解耦,网络请求的方式get,post等等,网络请求的具体实现可以随意替换(即低耦合了).

    如果定义一个网络发送协议,而让其他具体的类去遵循并且实现具体的网络请求功能:

    ///网络请求协议
    protocol PJClient {
        func send<T: PJRequest>(_ r: T, success: @escaping PJSuccess, fatalError: @escaping PJFatalError)
        func sendRequestForStruct<T: PJRequest>(_ r: T, success: @escaping PJSuccessForStruct<T>, fatalError: @escaping PJFatalError)
    }
    

    这样以后哦要替换底层网络具体实现时就很容易了,并且不会影响到现有的网络请求相关业务逻辑.

    ///默认的网络请求实现结构体
    struct PJHttpRequestClient: PJClient {
        
        ///用于网络请求的数据要转成struct类型的模型
        func sendRequestForStruct<T: PJRequest>(_ r: T, success: @escaping PJSuccessForStruct<T>, fatalError: @escaping PJFatalError) {
            self.send(r, success: { (model, response) -> Void in
                if let response = response as? DataResponse<Any>, let data = response.data, let jsonString = String(data:data, encoding: String.Encoding.utf8) {
                    let object = T.Response.parseStruct(jsonString: jsonString)
                    success(object, response)
                } else {
                    success(T.Response.parseStruct(jsonString: ""), response)
                }
            }) { (error) -> Void in
                fatalError(error)
            }
        }
        
        ///用于网络请求的数据要转成class类型的模型
        func send<T: PJRequest>(_ r: T, success: @escaping PJSuccess, fatalError: @escaping PJFatalError) {
            let url = r.host.appending(r.path)
            let request: DataRequest = Alamofire.request(url, method: r.httpMethod, parameters: r.parameter, encoding: URLEncoding.default, headers: r.headers)
            
            switch r.responseDataType {
            case .json:
                request.responseJSON(completionHandler: { (response : DataResponse<Any>) in
                    self.responseHandle(r, response: response, success: success, fatalError: fatalError)
                })
                break
            case .string:
                request.responseString(completionHandler: { (response : DataResponse<String>)  in
                    self.responseHandle(r, response: response, success: success, fatalError: fatalError)
                })
                break
            case .data:
                request.responseData(completionHandler: { (response : DataResponse<Data>) in
                    self.responseHandle(r, response: response, success: success, fatalError: fatalError)
                })
                break
            }
        }
        
        /*****解析服务器返回的数据*****/
        func responseHandle<T: PJRequest, P>(_ r: T, response : DataResponse<P>, success: @escaping PJSuccess, fatalError: @escaping PJFatalError) {
            if response.result.isSuccess {
                
                if let data = response.data, let jsonString = String(data:data, encoding: String.Encoding.utf8) {
                    PJPrintLog("请求成功结果JSON: \(jsonString)")
                    let className:String = NSStringFromClass(r.responseClass)
                    if let classType = NSClassFromString(className) as? PJBaseModel.Type {
                        let model = classType.init()
                        let object = model.parse(jsonString: jsonString)
                        success(object, response)
                    } else {
                        success(response.result.value, response)
                    }
                } else {
                    PJPrintLog("请求成功结果\(String(describing: response.result.value))")
                    success(response.result.value, response)
                }
            }else{
                PJPrintLog("请求失败结果error = \(String(describing: response.result.error))")
                fatalError(response.result.error)
            }
        }
    }
    

    PJHttpRequestClient是网络具体实现struct,其中

    func send<T: PJRequest>(_ r: T, success: @escaping PJSuccess, fatalError: @escaping PJFatalError)
    

    是实现PJClient的协议的方法,网络调用时类似:

    PJHttpRequestClient().send
    

    send函数里面的具体网络实现可以修改,用原生也好,第三方也好,只要能达到网络请求的目的,随意替换,而不用去大改代码,这就是面向协议的好处。

    把网络请求相关的配置也抽象出来也是比较灵活的

    ///网络请求配置协议
    protocol PJRequest {
        var path: String { get }
        var parameter: [String: Any] { get }
        var headers: HTTPHeaders { get }
        var httpMethod: HTTPMethod { get }
        var host: String { get }
        var responseDataType: PJResponseDataType { get }
        associatedtype Response: PJDecodable
        ///要转换的目标数据模型
        var responseClass: AnyClass { set get }
    }
    
    ///默认网络请求配置,用于网络请求返回数据转成class的模型
    struct PJBaseRequest<T: PJBaseModel>: PJRequest {
        var host: String = PJConst.PJBaseUrl
        var responseDataType: PJResponseDataType = .json
        var headers: HTTPHeaders = [:]
        var httpMethod: HTTPMethod = .get
        var path: String = ""
        var parameter: [String: Any] = [:]
        /// 如果需要改变类型,可以用子类重写改类型
        typealias Response = T
        var responseClass: AnyClass = T.classForCoder()
        
        ///responseClass:用于指定请求结果要转换的目标数据模型
        init(path: String, responseClass: AnyClass) {
            self.path = path
            self.responseClass = responseClass
        }
        
        init(path: String) {
            self.path = path
        }
    }
    
    ///默认网络请求配置,用于网络请求返回数据转成struct的模型
    struct PJBaseStrcutRequest<T: PJDecodable>: PJRequest {
        var host: String = PJConst.PJBaseUrl
        var responseDataType: PJResponseDataType = .json
        var headers: HTTPHeaders = [:]
        var httpMethod: HTTPMethod = .get
        var path: String = ""
        var parameter: [String: Any] = [:]
        /// 如果需要改变类型,可以用子类重写改类型
        typealias Response = T
        var responseClass: AnyClass = PJBaseModel.classForCoder()
        init(path: String) {
            self.path = path
        }
    }
    

    这里针对不同的返回处理结果(model用class或struct分别实现了协议,后面数据解析会用到),网络的请求部分大概是这样。

    数据解析

    显然数据解析也要达到解耦的目的,不管具体用第三方库还是自己一行一行写去解析数据,都是为了达到解析的目的,这样也采用协议,具体解析怎么实现可以随时替换,而不影响现有的解析好的。

    ///模型解析协议
    protocol PJDecodable {
        /// PJDecodable 用于解析class类型的模型,由于class不能继承static 静态方法,故使用普通成员方法
        func parse(jsonString: String) -> Self?
        /// PJDecodable 用于解析struct类型的模型
        static func parseStruct(jsonString: String) -> Self?
    }
    

    前面的protocol PJRequest有定义associatedtype Response: PJDecodable即是协议的泛型,表示网络请求返回后要解析转换后的目的模型。实现该协议时需要指定具体的目的类型struct PJBaseRequest<T: PJBaseModel>: PJRequest,这里我们希望代码可以复用故又加了一层泛型,这样/// 如果需要改变类型,可以用子类重写改类型typealias Response = T`,T即是目的解析类型,这样调用网络配置类时大概是这样:

    PJBaseRequest<Model>(path: requestUrl)
    

    每个model类只要去实现协议,并且实现具体的数据解析操作

    func parse(jsonString: String) -> Self? {
            let classType = type(of: self)
            if let baseModel = classType.deserialize(from: jsonString) {
                return baseModel
            }
            return nil
        }
        
        static func parseStruct(jsonString: String) -> Self? {
            let type = self
            if let baseModel = type.deserialize(from: jsonString) {
                return baseModel
            }
            return nil
        }
    

    这里数据解析使用HandyJSON,当然你大可以换其他的,因为很容易换。

    这样一个完整的网络的请求,返回数据解析是这样:

    ///请求的数据转成class(ExpressModel)
    var baseRequest = PJBaseRequest<ExpressModel>(path: self.requestUrl)
            baseRequest.headers = self.headers
            baseRequest.httpMethod = .get
            baseRequest.parameter = self.params
            PJHttpRequestClient().send(baseRequest, success: { (model, response) -> Void in
                if let model = model as? ExpressModel {               print("\(model)")
                }
            }) { (error) -> Void in
                return
            }
            
            ///请求的数据转成struct(ExpressModel2)
            var r = PJBaseStrcutRequest<ExpressModel2>(path: "query")
            r.parameter = self.getParams()
            PJHttpRequestClient().sendRequestForStruct(r, success: { (structModel, response) in
                if let model = structModel {
            print("\(model)")
                }
            }) { (error) in
    
            }
    

    其中var baseRequest = PJBaseRequest<ExpressModel>是网络请求配置,ExpressModel(可以替换成任意实现PJDecodable解析协议的类)是要解析转换的目的类型,这样网络请求完拿到的数据即是解析转换好的数据。var r = PJBaseStrcutRequest<ExpressModel2>(path: "query")是正对解析目的类型是struct的,ExpressModel2及时和目的struct,可以替换成任意实现PJDecodable解析协议的struct.网络请求与数据解析到此结束。

    tableView网络请求的封装

    发起网络请求,请求到数据,更新dataSource,reload,cell创建,设置好model,这大概是tableView显示的一贯流程,哪个地方要用到,就把代码复制一份过去。故我这边把这些可以复用的代码都封装到一个父类,需要用到tableView时,只要做一些必要配置一个网络请求你,数据解析,设置,显示的tableView便呈现在我们面前。

    具体的用法大概这样:

    前期必要设置

    dataSource,实现PJBaseTableViewDataSourceAndDelegate协议,PJBaseTableViewDataSourceAndDelegate协议是对tableView的dataSource的抽出提取,以减小controller大小

    class PJTableViewDemoDataSource: PJBaseTableViewDataSourceAndDelegate{
        // MARK: /***********必须重写以告诉表格什么数据模型对应什么cell*************/
        override func tableView(tableView: UITableView, cellClassForObject object: AnyObject?) -> AnyClass {
            if let _ = object?.isKind(of: ExpressItemModel.classForCoder()){
                return ExpressTableViewCell.classForCoder()
            }
            return super.tableView(tableView: tableView, cellClassForObject: object)
        }
    }
    

    只要实现这么一个方法,在控制器中:

    lazy var pjTableViewDemoDataSource : PJTableViewDemoDataSource = {
            let tempDataSource = PJTableViewDemoDataSource(dataSourceWithItems: nil)
            // TODO: /*******cell点击事件*******/
            tempDataSource.cellClickClosure = {
                (tableView:UITableView,indexPath : IndexPath,cell : UITableViewCell,object : Any?) in
                PJSVProgressHUD.showSuccess(withStatus: "点击了cell")
            }
            
            // TODO: /************cell的子控件的点击事件************/
            tempDataSource.subVieClickClosure = {
                (sender:AnyObject?, object:AnyObject?) in
                
            }
            return tempDataSource
        }()
        
        /**
         *  网络请求配置,子类可以重写,如果有需要
         */
    //    override func getBaseRequest() -> PJBaseRequest<PJBaseModelViewController.ModelType> {
    //        var baseRequest = PJBaseRequest<PJBaseModelViewController.ModelType>(path: self.requestUrl, responseClass: self.getModelClassType())
    //        baseRequest.headers = self.headers
    //        baseRequest.httpMethod = .get
    //        baseRequest.parameter = self.params
    //        return baseRequest
    //    }
        
        /**
         *   第二步:子类重写,网络请求完成
         */
        override func requestDidFinishLoad(success: Any?, failure: Any?) {
            if let expressModel = success as? ExpressModel {
                self.updateView(expressModel: expressModel)
            }
        }
        
        /**
         *   子类重写,网络请求失败
         */
        override func requestDidFailLoadWithError(failure: Any?) {
            
        }
        
        /**
         *   子类重写,以设置tableView数据源
         */
        override func createDataSource(){
            self.dataSourceAndDelegate = self.pjTableViewDemoDataSource
        }
        
        // MARK: 网络请求地址
        override func getRequestUrl() -> String{
            return "query"
        }
        
        // MARK: 网络请求参数
        override func getParams() -> [String:Any] {
            return ["type":"shentong","postid":"3342625464825"]
        }
        
        /// 获取返回的数据的模型类型
        ///
        /// - Returns: 获取返回的数据的模型类型
        override func getModelClassType() -> AnyClass {
            return ExpressModel.classForCoder()
        }
    

    一个带有分页,数据为空,数据显示,网络请求,数据解析,显示的tableView变完成了。

    self.doRequest()
    

    发起网络请求,一个完整的tableview网络请求变搞定。当然可以定制,修改这边不一一列举。今天就到这里,大概是这样,代码和思路大量借鉴🐱神(福建文档写的最烂的男人😜)

    相关文章

      网友评论

          本文标题:一个可以快速开发的代码封装框架--PJQuicklyDev2

          本文链接:https://www.haomeiwen.com/subject/gxvbvftx.html