Skip to content

Instantly share code, notes, and snippets.

@shingohry
Created June 11, 2019 17:13
Show Gist options
  • Save shingohry/77f08a9a2a414164df8b1b00fcaba549 to your computer and use it in GitHub Desktop.
Save shingohry/77f08a9a2a414164df8b1b00fcaba549 to your computer and use it in GitHub Desktop.
NetworkingWithCombine
import UIKit
import Combine
/*
# References
- [ra1028/SwiftUI-Combine: This is an example project of SwiftUI and Combine using GitHub API.](https://github.com/ra1028/SwiftUI-Combine)
- [marty-suzuki/GitHubSearchWithSwiftUI: SwiftUI and Combine based GitHubSearch example.](https://github.com/marty-suzuki/GitHubSearchWithSwiftUI)
- [DataTaskPublisherを作ってみた](https://gist.github.com/yamoridon/16c1cc70ac46e50def4ca6695ceff772)
- [【iOS】Combineフレームワークまとめ(2019/6/9時点) - Qiita](https://qiita.com/shiz/items/5efac86479db77a52ccc)
*/
// MARK: - ViewController
class ViewController: UIViewController {
let decorder: JSONDecoder = {
let decorder = JSONDecoder()
decorder.keyDecodingStrategy = .convertFromSnakeCase
return decorder
}()
var requestCancellable: Cancellable?
deinit {
requestCancellable?.cancel()
}
override func viewDidLoad() {
super.viewDidLoad()
request()
}
}
extension ViewController {
func request() {
let request = URLRequest(url: URL(string: "https://api.github.com/search/repositories?q=swift+combine")!)
requestCancellable = URLSession.shared.publisher(for: request)
.decode(type: Repositories.self, decoder: decorder) // decode to struct Repositories
.sink(receiveCompletion: { completion in // Attaches a subscriber with closure-based behavior.
switch completion {
case .finished:
print("finished")
case .failure(let error):
print("error:\(error.localizedDescription)")
}
}, receiveValue: { repositories in
print("repositories:\(repositories)")
})
}
}
// MARK: - Publisher
struct RequestPublisher: Publisher {
typealias Output = Data
typealias Failure = RequestError
let session: URLSession
let request: URLRequest
// This function is called to attach the specified Subscriber to this Publisher by subscribe(_:)
func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
let task = session.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
let httpReponse = response as? HTTPURLResponse
if let data = data, let httpReponse = httpReponse, 200..<300 ~= httpReponse.statusCode {
// Tells the subscriber that the publisher has produced an element.
_ = subscriber.receive(data)
// Tells the subscriber that the publisher has completed publishing.
subscriber.receive(completion: .finished)
} else if let httpReponse = httpReponse {
// Tells the subscriber that the publisher has completed publishing with a HTTP error.
let error = RequestError.http(code: httpReponse.statusCode, error: error)
subscriber.receive(completion: .failure(error))
} else {
// Tells the subscriber that the publisher has completed publishing with a other error.
let error = RequestError.other
subscriber.receive(completion: .failure(error))
}
}
}
// Tells the subscriber that it has successfully subscribed to the publisher and may request items.
let subscription = RequestSubscription(combineIdentifier: CombineIdentifier(), task: task)
subscriber.receive(subscription: subscription)
task.resume()
}
}
extension URLSession {
// return RequestPublisher
func publisher(for request: URLRequest) -> RequestPublisher {
return RequestPublisher(session: self, request: request)
}
}
struct RequestSubscription: Subscription {
let combineIdentifier: CombineIdentifier
let task: URLSessionTask
func request(_ demand: Subscribers.Demand) {}
func cancel() {
task.cancel()
}
}
// MARK: - Models
enum RequestError: Error {
case http(code: Int, error: Error?)
case other
}
struct Repositories: Codable {
let totalCount: Int
let items: [Repository]
}
struct Repository: Codable {
let id: Int
let name: String
let htmlUrl: String
}
extension JSONDecoder: TopLevelDecoder {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment