Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save SergLam/7c1fdd2ac53c70d78b76e3af736e91fa to your computer and use it in GitHub Desktop.
Save SergLam/7c1fdd2ac53c70d78b76e3af736e91fa to your computer and use it in GitHub Desktop.
Subclass of (NS)Operation to make it asynchronous in Swift 5, also providing a Block-based alternative.
//
// AsynchronousBlockOperation.swift
//
// Created by Jose Canepa on 12/13/17.
// Copyright © 2017 Jose Canepa. All rights reserved.
//
import Foundation
/// Block based version of the AsynchronousOperation
///
/// Has the same qualities as the AsynchronousOperation, but with an implementation
/// closer to the one of a BlockOperation.
///
/// Once the inner asynchronous operation is finished, call the finish()
/// on the block reference passed. Do not retain the passed block.
///
final class AsynchronousBlockOperation : AsynchronousOperation
{
private var block : (AsynchronousBlockOperation) -> ()
init(_ block : @escaping (AsynchronousBlockOperation) -> ())
{
self.block = block
}
override func main()
{
// If we were executing already
let wasExecuting = (state == .executing)
// Call our main to ensure we are not cancelled
super.main()
if !wasExecuting
{
block(self)
}
}
}
//
// AsynchronousOperation.swift
//
// Created by Vasily Ulianov on 09.02.17.
// Copyright © 2017 Vasily Ulianov. All rights reserved.
//
import Foundation
/// Subclass of `Operation` that add support of asynchronous operations.
/// ## How to use:
/// 1. Call `super.main()` when override `main` method, call `super.start()` when override `start` method.
/// 2. When operation is finished or cancelled call `finish()`
open class AsynchronousOperation: Operation
{
public enum State : String
{
case ready = "Ready"
case executing = "Executing"
case finished = "Finished"
fileprivate var keyPath: String { return "is" + self.rawValue }
}
override open var isAsynchronous : Bool { return true }
override open var isExecuting : Bool { return state == .executing }
override open var isFinished : Bool { return state == .finished }
public private(set) var state = State.ready
{
willSet(n)
{
willChangeValue(forKey: state.keyPath)
willChangeValue(forKey: n.keyPath)
}
didSet(o)
{
didChangeValue(forKey: state.keyPath)
didChangeValue(forKey: o.keyPath)
}
}
override open func start()
{
if self.isCancelled
{
state = .finished
}
else
{
state = .ready
main()
}
}
override open func main()
{
if self.isCancelled
{
state = .finished
}
else
{
state = .executing
}
}
public func finish()
{
guard state != .finished else { return }
state = .finished
}
}
public typealias DataTaskCompletion = (Data?, URLResponse?, Error?) -> Void
public typealias DownloadTaskCompletion = (URL?, URLResponse?, Error?) -> Void
import Foundation
public final class NetworkOperation: Operation {
public enum State: String {
case ready = "Ready"
case executing = "Executing"
case finished = "Finished"
fileprivate var keyPath: String {
return "is" + self.rawValue
}
}
/// Identifier that is used for URLSessionTask `taskDescription` property setup.
/// Helps to identify a network request among other requests in URLSesion.
public var identifier: UUID = UUID()
/// Use this completion handler for a correct data parsing and error handling.
/// Avoid system-provided `competionBlock` usage.
public var taskCompletion: DataTaskCompletion?
override public var isAsynchronous : Bool { return true }
override public var isExecuting : Bool { return state == .executing }
override public var isFinished : Bool { return state == .finished }
public private(set) var state = State.ready {
willSet(newValue) {
willChangeValue(forKey: state.keyPath)
willChangeValue(forKey: newValue.keyPath)
}
didSet(oldValue) {
didChangeValue(forKey: state.keyPath)
didChangeValue(forKey: oldValue.keyPath)
}
}
// MARK: - Request data
private let urlSession: URLSession
private(set) var request: URLRequest
private let uploadData: Data?
private var networkTask: URLSessionTask?
// MARK: - Response data
private(set) var responseData: Data?
private(set) var response: URLResponse?
private(set) var responseError: Error?
init(urlSession: URLSession,
request: URLRequest,
uploadData: Data? = nil) {
self.urlSession = urlSession
self.request = request
self.uploadData = uploadData
super.init()
name = identifier.uuidString
}
override public func start() {
if self.isCancelled {
state = .finished
} else {
state = .ready
main()
}
}
override public func main() {
if self.isCancelled {
state = .finished
} else {
state = .executing
if let data = uploadData {
networkTask = urlSession.uploadTask(with: request, from: data) { [weak self] (data, response, error) in
self?.responseData = data
self?.response = response
self?.responseError = error
self?.taskCompletion?(data, response, error)
self?.finish()
}
} else {
networkTask = urlSession.dataTask(with: request, completionHandler: { [weak self] (data, response, error) in
self?.responseData = data
self?.response = response
self?.responseError = error
self?.taskCompletion?(data, response, error)
self?.finish()
})
}
networkTask?.resume()
}
}
override public func cancel() {
networkTask?.cancel()
networkTask = nil
state = .finished
}
public func finish() {
guard state != .finished else {
return
}
state = .finished
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment