在swift项目中实现网络请求层的思路
刚开始写swift的时候是用写OC的思路方法在写swift,OC里面写网络请求的思路是,封装一个网络请求类(以下简称A),业务代码通过调用这个网络请求类来实现网络请求。这样会有一个问题,就是你的业务代码里面调用网络请求的时候回四处出现类似于"/user/login"之类的传参,很不优雅,看起来也很是碎片化,尤其是采用MVC的时候,VC里面可能会充斥多个这样的玩意,显得很业余。
你可能会说,可以把各个业务模块的网络请求单独封装成类(以下简称B),它是对A的一层封装和定制,这样在业务代码里面就看不到乱七八糟的东西了。这确实是一种解决办法,但是这样会使得你的每个网络请求里面的数据解析代码一遍一遍的去写,重复性的工作交给代码去做不好吗?
在上面的请求方式中,有两个弊端:
第一,如果这个请求方法不是很符合我的需求,比如需要加header,或者解析方式有点不大一样,就需要直接去改你所封装的网络请求类的代码,或者在封装的网络请求类里面增加新的解析代码,这样入侵性会比较强。或者你也可以继承网络请求类来重写他的方法或者增加方法,这样的确可以解决问题,但是会加剧工程代码的碎片化。
第二,如果你采用MVVM的架构,这样会增加你的代码分层,其实不只是MVVM架构,因为你的业务代码是没有网络请求能力的,只能通过调用A来实现网络请求的功能,请求到数据要通过接口抛给接受者。虽然我们鼓励代码分层,这样可以让结构更清晰,但是我们也会想办法在保证解耦和清晰的前提下减少层数。
在swift项目里面写,当然要充分利用swift的特性,swift推崇的是面向协议编程,不了解swift的面向协议,看看这个:
Swift 【基于 Swift 面向协议编程】
里面详细介绍了swift面向协议的思想。
我的代码里用到的JSON解析库是:
HandyJSON
swift版的AFN:
Alamofire
废话不多说,看看如何用swift解决这个问题。
首先是协议的定义:
/**
设置请求参数的协议接口
*/
protocol NetworkParametersProtocol {
var requestUrl: String?{set get}
func setRequestUrl()-> String
func setRequestBody()-> [String:Any]
}
protocol PostListNetworkProtocol:NetworkParametersProtocol
这样写是因为协议是可以继承的,继承了NetworkParametersProtocol这个协议之后,只要是遵守PostListNetworkProtocol这个协议的类,都会有NetworkParametersProtocol的默认实现,这个实现是可以自定义的。
protocol PostListNetworkProtocol:NetworkParametersProtocol {
/**
请求列表数据(post)
*/
func startListRequest<T:HandyJSON>(successionHandler: @escaping ([T],_ total:Int) -> Void, failerHandler: @escaping () -> Void)
}
protocol PostBeenNetworkProtocol:NetworkParametersProtocol {
/**
请求非列表数据(post)
*/
func startBeenRequest<T:HandyJSON>(successionHandler: @escaping (T) -> Void, failerHandler: @escaping () -> Void)
}
protocol GetListNetworkProtocol:NetworkParametersProtocol {
/**
请求列表数据(get)
*/
func getListRequest<T:HandyJSON>(successionHandler: @escaping ([T],_ total:Int) -> Void, failerHandler: @escaping () -> Void)
}
protocol GetBeenNetworkProtocol:NetworkParametersProtocol {
/**
请求非列表数据(get)
*/
func getBeenRequest<T:HandyJSON>(successionHandler: @escaping (T) -> Void, failerHandler: @escaping () -> Void)
}
protocol CancelNetworkProtocol:NetworkParametersProtocol {
/**
终止网络请求
*/
func terminateNetwork()
}
接下来给这些协议提供默认实现:
extension PostListNetworkProtocol {
/**
请求列表数据(post)
*/
func startListRequest<T:HandyJSON>(successionHandler: @escaping ([T],_ total:Int) -> Void, failerHandler: @escaping () -> Void){
NetworkFilterOrganizer.shared.postWithArguements(url:setRequestUrl(), header: ["token": UserUtil.isLogin() ? UserUtil.getCurrentUser().token! : "","Content-Type":"application/json"], parameters: setRequestBody(), completionCallBack: {
ZKProgressHUD.dismiss()
}, sucessCallBack: { (response) in
let dic = response["data"] as! NSDictionary
let total = dic["total"] as! Int
if let arr:[T] = [T].deserialize(from: dic["list"] as? NSArray) as? [T] {
successionHandler(arr,total)
}else{
failerHandler()
}
}) { (error) in
failerHandler()
}
}
}
extension PostBeenNetworkProtocol{
/**
请求非列表数据(post)
*/
func startBeenRequest<T:HandyJSON>(successionHandler: @escaping (T) -> Void, failerHandler: @escaping () -> Void){
NetworkFilterOrganizer.shared.postWithArguements(url:setRequestUrl(), header: ["token":UserUtil.isLogin() ? UserUtil.getCurrentUser().token! : "","Content-Type":"application/json"], parameters: setRequestBody(), completionCallBack: {
ZKProgressHUD.dismiss()
}, sucessCallBack: { (response) in
if let been:T = T.deserialize(from: response["data"] as? NSDictionary) {
successionHandler(been)
}else {
let been:T = T()
successionHandler(been)
}
}) { (error) in
failerHandler()
}
}
}
extension GetListNetworkProtocol{
/**
请求列表数据(get)
*/
func getListRequest<T:HandyJSON>(successionHandler: @escaping ([T],_ total:Int) -> Void, failerHandler: @escaping () -> Void){
NetworkFilterOrganizer.shared.getWithArguements(url:self.setRequestUrl(), parameters: self.setRequestBody(), headers: ["token":UserUtil.isLogin() ? UserUtil.getCurrentUser().token! : ""], sucessCallBack: { (response) in
TBTHUD.dismiss()
if let dic = response["data"] as? NSDictionary{
let total = dic["total"] as! Int
if let arr:[T] = [T].deserialize(from: dic["list"] as? NSArray) as? [T] {
successionHandler(arr,total)
}else{
failerHandler()
}
}else{
if let arr = response["data"] as? NSArray{
let result:[T] = ([T].deserialize(from: arr) as? [T])!
successionHandler(result,result.count)
}
}
}) { (error) in
TBTHUD.dismiss()
failerHandler()
}
}
}
extension GetBeenNetworkProtocol{
/**
请求非列表数据(get)
*/
func getBeenRequest<T:HandyJSON>(successionHandler: @escaping (T) -> Void, failerHandler: @escaping () -> Void){
NetworkFilterOrganizer.shared.getWithArguements(url:self.setRequestUrl(), parameters: self.setRequestBody(), headers: ["token":UserUtil.isLogin() ? UserUtil.getCurrentUser().token! : ""], sucessCallBack: { (response) in
TBTHUD.dismiss()
if let been:T = T.deserialize(from: response["data"] as? NSDictionary) {
successionHandler(been)
}else {
let been:T = T()
successionHandler(been)
}
}) { (error) in
TBTHUD.dismiss()
failerHandler()
}
}
}
extension CancelNetworkProtocol where Self:NetworkParametersProtocol{
/**
取消指定的网络请求
*/
func terminateNetwork(){
Network.shared.cancleRequestWithUrlString(url: self.setRequestUrl())
}
}
NetworkFilterOrganizer是发起网络请求前需要经过的一个过滤器,这是我的项目的特殊需求决定的,一般的逻辑是在这里直接调请求方法的。
可能你会觉得我在这里写的代码太多了,分的太细了,这里写这么多,其实是为了在别的地方不写,接着往下看。
新建一个BaseNetworkService,就是网络请求的基类,新建基类的目的是使用它的deinit方法来取消当前的网络请求,否则如果一个页面网络请求时间过久,用户pop了页面,很容易导致crash,如果有更好的办法,请各位大神赐教。
class BaseNetworkService: CancelNetworkProtocol{
var url = ""
var requestUrl: String?
func setRequestBody() -> [String : Any] {
return [:]
}
func setRequestUrl() -> String {
return self.requestUrl!
}
deinit {
terminateNetwork()
}
}
在具体业务要发起请求的时候,只需要新建一个Service类,继承BaseNetworkService,比如这个业务接口是Post请求一个列表,那它就声明一下PostListNetworkProtocol这个协议,那么它就具有了post请求列表的能力
class LVVideoCollectionService: BaseNetworkService,PostListNetworkProtocol{
var currentPage = 1
var pageSize = 20
override func setRequestUrl() -> String {
return "user/collectList"
}
override func setRequestBody() -> [String : Any] {
return ["currentPage": currentPage , "pageSize":pageSize]
}
}
网络请求的时候需要创建一个这样的service对象,然后发起请求就好
self.loadDataService.startListRequest(successionHandler: {[weak self] (response:[LVVideoListModel], total) in//由于在协议定义的时候采用了泛型,只要LVVideoListModel这个类遵守了HandyJSON这个协议,就能直接强转,不会出错的。
self!.collectionList.append(contentsOf: response)
self!.isLastPage = (self?.collectionList.count)! >= total
successCallBack((self?.isLastPage)!)
}) {
failCallBack()
}
这样,网络请求,数据解析的代码只用写一次,后期完全通过协议调用,如果有特别的需求,只需要重新定义协议或者重写协议实现就行。
本文只是抛砖引玉,大神轻喷。