Skip to content

Instantly share code, notes, and snippets.

@yesleon
Created May 15, 2019 08:06
Show Gist options
  • Save yesleon/15f0841a89749948c13968ad6e14aaae to your computer and use it in GitHub Desktop.
Save yesleon/15f0841a89749948c13968ad6e14aaae to your computer and use it in GitHub Desktop.
import Foundation
// 基礎協定
// 每個套用 Endpoint 的型別都有固定的回傳型別,所以如果回傳的型別不同,就要宣告不同的 Endpoint 型別。
protocol Endpoint {
// Endpoint 所關聯的回傳型別
associatedtype Result: Codable
// URLRequest 建構式
func makeRequest() throws -> URLRequest
}
// 基礎方法
extension URLSession {
// 用 Generics 來關聯 endpoint 與 result 的型別。
func retrieveData<T>(from endpoint: T, configurationHandler: (inout URLRequest) throws -> () = { _ in }, completion: @escaping (Result<T.Result, Error>) -> Void) where T: Endpoint {
do {
// 建構 URLRequest。
var request = try endpoint.makeRequest()
// 給呼叫者一個修改 request 的機會(拿來加參數之類的)。
try configurationHandler(&request)
// 呼叫
let task = dataTask(with: request) { data, response, error in
let result = Result<T.Result, Error> {
if let error = error {
throw error
}
if let data = data {
return try JSONDecoder().decode(T.Result.self, from: data)
} else {
throw "Data is nil"
}
}
completion(result)
}
task.resume()
} catch {
completion(.failure(error))
}
}
}
// 便利方法
extension String: Error { }
extension URL {
// 方便存取 URL 的參數。
var queryItems: [URLQueryItem]? {
get {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { return nil }
return components.queryItems
}
set {
guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { return }
components.queryItems = newValue
guard let url = components.url else { return }
self = url
}
}
}
// 使用範例
// 如果回傳的型別相同,即可用 enum 共用一個 Endpoint 型別。
enum ArticlesEndpoint: Endpoint {
// 這裡直接用 nested type 去遵守 Endpoint 的 associatedType 要求。
struct Result: Codable {
var articles: [Article]
}
// 會回傳相同型別的不同 endpoint case。
case everything, topHeadlines
static let apiKey = URLQueryItem(name: "apiKey", value: "2d7e87c80eb9454ea379c0c20d67aec0")
// 根據不同的 endpoint case,建構不同的 URLRequest。
func makeRequest() throws -> URLRequest {
switch self {
case .everything:
guard var url = URL(string: "https://newsapi.org/v2/everything") else { throw "Failed to construct URL" }
url.queryItems = [ArticlesEndpoint.apiKey]
return .init(url: url)
case .topHeadlines:
guard var url = URL(string: "https://newsapi.org/v2/top-headlines") else { throw "Failed to construct URL" }
url.queryItems = [ArticlesEndpoint.apiKey]
return .init(url: url)
}
}
}
// 資料型別
struct Article: Codable {
var title: String
var description: String
var url: URL
var publishedAt: String
}
// 直接使用 URLSession 來呼叫。
URLSession.shared.retrieveData(from: ArticlesEndpoint.topHeadlines, configurationHandler: { request in
// 注入參數。因為 request 是 inout 參數所以不須回傳。
request.url?.queryItems?.append(.init(name: "country", value: "tw"))
}) { result in
do {
// 獲取結果。
let result = try result.get()
// 結果已經是 ArticlesEndpoint.Result 型別了,不須再轉換。
print(result.articles)
} catch {
print(error)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment