Created
May 15, 2019 08:06
-
-
Save yesleon/15f0841a89749948c13968ad6e14aaae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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