Skip to content

Instantly share code, notes, and snippets.

@westerlund
Last active July 13, 2017 13:06
Show Gist options
  • Save westerlund/eb71eb4ba30a52a022f238f9d409dcc8 to your computer and use it in GitHub Desktop.
Save westerlund/eb71eb4ba30a52a022f238f9d409dcc8 to your computer and use it in GitHub Desktop.
//
// 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