Last active
June 27, 2019 06:52
-
-
Save yesleon/e8b8ff4920d94fc00b003e60cb942d4a to your computer and use it in GitHub Desktop.
A simple wrapper for enabling reactive programming in Swift. Some functionalities require Swift >= 5.1.
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
// MARK: Core | |
public typealias DeactivateHandler = () -> Void | |
public struct AsyncTask<Success, Failure> where Failure: Error { | |
public var activate: (@escaping (Success) -> Void, @escaping (Failure) -> Void) -> DeactivateHandler | |
} | |
extension AsyncTask where Failure == Never { | |
public func activate(successHandler: @escaping (Success) -> Void) -> DeactivateHandler { | |
self.activate(successHandler, { _ in }) | |
} | |
} | |
extension AsyncTask { | |
public func map<NewSuccess>(_ transform: @escaping (Success) -> NewSuccess) -> AsyncTask<NewSuccess, Failure> { | |
.init { successHandler, failureHandler in | |
self.activate({ successHandler(transform($0)) }, failureHandler) | |
} | |
} | |
public func tryMap<NewSuccess>(_ transform: @escaping (Success) throws -> NewSuccess) -> AsyncTask<NewSuccess, Error> { | |
.init { successHandler, failureHandler in | |
self.activate({ | |
do { | |
successHandler(try transform($0)) | |
} catch { | |
failureHandler(error) | |
} | |
}, failureHandler) | |
} | |
} | |
public func flatMap<NewSuccess>(_ transform: @escaping (Success) -> AsyncTask<NewSuccess, Failure>) -> AsyncTask<NewSuccess, Failure> { | |
.init { successHandler, failureHandler in | |
var newCancelHandler: DeactivateHandler? | |
let oldCancelHandler = self.activate({ | |
newCancelHandler = transform($0).activate(successHandler, failureHandler) | |
}, failureHandler) | |
return { | |
oldCancelHandler() | |
newCancelHandler?() | |
} | |
} | |
} | |
public func mapError<NewFailure>(_ transform: @escaping (Failure) -> NewFailure) -> AsyncTask<Success, NewFailure> { | |
.init { successHandler, failureHandler in | |
self.activate(successHandler, { failureHandler(transform($0)) }) | |
} | |
} | |
public func compactMap<NewSuccess>(_ transform: @escaping (Success) -> NewSuccess?) -> AsyncTask<NewSuccess, Failure> { | |
.init { successHandler, failureHandler in | |
self.activate({ transform($0).map(successHandler) }, failureHandler) | |
} | |
} | |
} | |
// MARK: Implementations | |
// MARK: URLSession | |
import Foundation | |
extension URLSession { | |
public func dataTask(for request: URLRequest) -> AsyncTask<(data: Data, response: URLResponse), URLError> { | |
.init { successHandler, failureHandler in | |
let task = self.dataTask(with: request) { data, response, error in | |
if let error = error as? URLError { | |
failureHandler(error) | |
} else if let data = data, | |
let response = response { | |
successHandler((data: data, response: response)) | |
} | |
} | |
task.resume() | |
return { | |
task.cancel() | |
} | |
} | |
} | |
} | |
// MARK: NotificationCenter | |
extension NotificationCenter { | |
public func observation(for name: Notification.Name, object: AnyObject? = nil) -> AsyncTask<Notification, Never> { | |
.init { successHandler, _ in | |
let observation = self.addObserver(forName: name, object: object, queue: nil, using: successHandler) | |
return { [weak self] in | |
self?.removeObserver(observation) | |
} | |
} | |
} | |
} | |
// MARK: PropertyObservable | |
@propertyDelegate | |
public struct Reference<Value> { | |
public var value: Value { | |
get { | |
getter() | |
} | |
nonmutating set { | |
setter(newValue) | |
} | |
} | |
private let getter: () -> Value | |
private let setter: (Value) -> Void | |
public init(initialValue: Value) { | |
var value = initialValue | |
getter = { value } | |
setter = { value = $0 } | |
} | |
} | |
@propertyDelegate | |
public struct Observable<Value> { | |
public var value: Value { | |
didSet { | |
handler?(value) | |
} | |
} | |
@Reference var handler: ((Value) -> Void)? = nil | |
public init(initialValue: Value) { | |
self.value = initialValue | |
} | |
public func observation() -> AsyncTask<Value, Never> { | |
.init { successHandler, _ in | |
let oldHandler = self.handler | |
self.handler = { | |
oldHandler?($0) | |
successHandler($0) | |
} | |
return { | |
self.handler = oldHandler | |
} | |
} | |
} | |
} | |
// MARK: KVO | |
public protocol KeyValueObservable { } | |
extension NSObject: KeyValueObservable { } | |
extension KeyValueObservable where Self: NSObject { | |
public func observation<Value>(for keyPath: KeyPath<Self, Value>, options: NSKeyValueObservingOptions = [.initial, .new]) -> AsyncTask<Value, Never> { | |
.init { successHandler, _ in | |
var observation: NSKeyValueObservation? | |
observation = self.observe(keyPath, options: options) { (object, change) in | |
_ = observation | |
change.newValue.map(successHandler) | |
} | |
return { | |
observation = nil | |
} | |
} | |
} | |
} | |
// MARK: UIControl | |
import UIKit | |
class ControlTarget: NSObject { | |
var actionHandler: ((sender: Any, event: UIControl.Event)) -> Void = { _ in } | |
@objc func action(sender: Any, event: UIControl.Event) { | |
actionHandler((sender: sender, event: event)) | |
} | |
} | |
extension UIControl { | |
public func observation(for event: Event) -> AsyncTask<(sender: Any, event: UIControl.Event), Never> { | |
.init { successHandler, _ in | |
let target = ControlTarget() | |
let action = #selector(ControlTarget.action(sender:event:)) | |
self.addTarget(target, action: action, for: event) | |
target.actionHandler = { | |
_ = target | |
successHandler($0) | |
} | |
return { | |
target.actionHandler = { _ in } | |
self.removeTarget(target, action: action, for: event) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment