Last active
July 13, 2017 13:06
-
-
Save westerlund/eb71eb4ba30a52a022f238f9d409dcc8 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
// URLSessionOperation.swift | |
// time | |
// | |
// Created by Simon Westerlund on 2016-10-23. | |
// Copyright © 2016 Simon Westerlund. All rights reserved. | |
// | |
import UIKit | |
typealias JSONDictionary = [String: Any] | |
typealias JSONArray = [JSONDictionary] | |
final class URLSessionOperation: Operation, URLSessionDataDelegate { | |
enum URLSessionOperationError: Error { | |
case noData, invalidStructure, previousOperationFailed, unknown | |
} | |
private(set) var dataTask: URLSessionDataTask! | |
private(set) var response: URLResponse! | |
private(set) var error: Error? | |
private(set) var json: JSONArray? | |
private var _isFinished = false { | |
willSet { willChangeValue(forKey: "isFinished") } | |
didSet { didChangeValue(forKey: "isFinished") } | |
} | |
private var _isExecuting = false { | |
willSet { willChangeValue(forKey: "isExecuting") } | |
didSet { didChangeValue(forKey: "isExecuting") } | |
} | |
private var _isCancelled = false { | |
willSet { willChangeValue(forKey: "isCancelled") } | |
didSet { didChangeValue(forKey: "isCancelled") } | |
} | |
/// A list of operations that _must_ not be failed in order to start this operation | |
private var requiredNonFailedOperations: [FailableOperation] = [] | |
private var data: Data? | |
init(request: URLRequest, operationQueue: OperationQueue? = OperationQueue.current) { | |
super.init() | |
let configuration = URLSessionConfiguration.default | |
configuration.timeoutIntervalForRequest = 30.0 | |
let session = URLSession(configuration: configuration, | |
delegate: self, | |
delegateQueue: OperationQueue.current) | |
dataTask = session.dataTask(with: request) | |
} | |
/// Add a dependency operation and if `startIfFailed` is false, don't start this operation if the dependency fails | |
func addDependency(_ op: FailableOperation, startIfFailed: Bool) { | |
addDependency(op as! Operation) | |
if startIfFailed == false { | |
requiredNonFailedOperations.append(op) | |
} | |
} | |
private func parse(json: Data?) throws -> JSONArray? { | |
guard let data = data else { | |
throw URLSessionOperationError.noData | |
} | |
let json = try JSONSerialization.jsonObject(with: data, options: []) | |
if let dictionary = json as? JSONDictionary { | |
return [dictionary] | |
} else if let array = json as? JSONArray { | |
return array | |
} | |
throw URLSessionOperationError.invalidStructure | |
} | |
// MARK: URLSessionDelegate | |
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) { | |
self.response = response | |
completionHandler(.allow) | |
} | |
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) { | |
self.data = data | |
} | |
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { | |
if let error = error { | |
self.error = error | |
} else { | |
do { | |
json = try parse(json: data) | |
} catch (let jsonError) { | |
self.error = jsonError | |
} | |
} | |
done() | |
} | |
// MARK: Operation stuff | |
private var shouldStartOperation: Bool { | |
var shouldStart = true | |
requiredNonFailedOperations.forEach { op in | |
if op.isFailed { | |
shouldStart = false | |
} | |
} | |
return shouldStart | |
} | |
override func start() { | |
guard shouldStartOperation else { | |
cancel(with: .previousOperationFailed) | |
return | |
} | |
UIApplication.shared.isNetworkActivityIndicatorVisible = true | |
dataTask.resume() | |
} | |
func cancel(with error: URLSessionOperationError? = nil) { | |
if let error = error { | |
self.error = error | |
} | |
cancel() | |
} | |
override func cancel() { | |
_isCancelled = true | |
dataTask.cancel() | |
done() | |
} | |
private func done() { | |
_isExecuting = false | |
_isFinished = true | |
UIApplication.shared.isNetworkActivityIndicatorVisible = false | |
} | |
override var isExecuting: Bool { | |
return _isExecuting | |
} | |
override var isFinished: Bool { | |
return _isFinished | |
} | |
override var isCancelled: Bool { | |
return _isCancelled | |
} | |
} | |
protocol FailableOperation { | |
var isFailed: Bool { get } | |
} | |
extension URLSessionOperation: FailableOperation { | |
var isFailed: Bool { | |
return error != nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment