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()