本篇主要介绍如何建一个networkController进行API Call。首先是APIRequestConfigure
protocol APIRequestConfigure {
var url: String { get }
var cachePolicy: URLRequest.CachePolicy? { get }
var timeoutInterval: TimeInterval? { get }
var header: [String:String]? { get }
var httpBody: Data? { get }
var httpMethod: String? { get }
}
extension APIRequestConfigure {
func makeRequest() throws -> URLRequest {
guard let url = URL(string: url) else { throw NetworkError.invalidURL }
var request = URLRequest(url: url, cachePolicy: cachePolicy ?? URLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: timeoutInterval ?? 10)
request.allHTTPHeaderFields = header
request.httpBody = httpBody
request.httpMethod = httpMethod
return request
}
}
这个APIRequestConfigure主要是对API Request进行一个封装。这里除了URL,其他的parameter都是optional。
接下来是APIRequestLoader
class APIRequestLoader {
let apiRequest: APIRequestConfigure
let urlSession: Session
init(apiRequest: APIRequestConfigure, urlSession: Session = URLSession.shared) {
self.apiRequest = apiRequest
self.urlSession = urlSession
}
func loadRequest<T: Codable>(completionHandler: @escaping ((T?, Error?) -> Void)) {
guard let request = try? apiRequest.makeRequest() else {
completionHandler(nil, NetworkError.invalidRequest)
return
}
urlSession.getData(with: request) { (data, error) in
guard error == nil else {
completionHandler(nil, error)
return
}
guard let data = data else {
completionHandler(nil, NetworkError.noData)
return
}
do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
completionHandler(decodedData, nil)
} catch {
completionHandler(nil, NetworkError.invalidJSONData)
}
}
}
}
这个就是比较常规的用URLSession进行APICal了。在初始化中我把default的session设置为URLSession.share,方便如果需要用其他的framework的话,可以在这里进行插入。接下来是Session。
protocol Session {
func getData(with url: URL, completionHandler: @escaping (Data?, Error?) -> Void)
func getData(with request: URLRequest, completionHandler: @escaping(Data?, Error?) -> Void)
}
extension URLSession: Session {
func getData(with request: URLRequest, completionHandler: @escaping (Data?, Error?) -> Void) {
self.dataTask(with: request) { (data, _, error) in
completionHandler(data, error)
}.resume()
}
func getData(with url: URL, completionHandler: @escaping (Data?, Error?) -> Void) {
self.dataTask(with: url) { (data, _, error) in
completionHandler(data, error)
}.resume()
}
}
这里用Protocol作为一个abstract方便之后的使用,并把它作为subclass添加到了URLSession里,这样在使用URLSession的时候,也可以getData进行APICall。最后是NetworkRequestConfigure。
struct NetworkRequestConfigure: APIRequestConfigure {
var url: String
var cachePolicy: URLRequest.CachePolicy?
var timeoutInterval: TimeInterval?
var header: [String : String]?
var httpBody: Data?
var httpMethod: String?
}
用之前的APIRequestConfigure作为subclass,代入后就可以用这个NetworkRequestConfigure作为一个abstract方便之后的使用。
最后来看我是如何使用它的
private var apiRequestLoader: APIRequestLoader
init(apiRequestLoader: APIRequestLoader? = nil) {
if let apiRequestLoader = apiRequestLoader {
self.apiRequestLoader = apiRequestLoader
} else {
let ituneRssRequestConfig: APIRequestConfigure = ItunesRssRequestConfig()
let apiRequestLoader = APIRequestLoader(apiRequest: ituneRssRequestConfig)
self.apiRequestLoader = apiRequestLoader
}
}
在ViewModel里创建一个apiRequestLoader,然后在初始化里把它设置成optional。再用if let进行optional binding。如果在初始化里有apiRequestLoader,就等于它。如果没有,就等于我设置的这个viewModel该需要的ituneRssRequestConfig(这个ituneRss有APIRequestConfigure里多加了一个需要使用的URL)。下面是github
https://github.com/grm121616/NetworkController/tree/master/NetworkController