Skip to content

Instantly share code, notes, and snippets.

@yesleon
Last active June 27, 2019 06:52
Show Gist options
  • Save yesleon/e8b8ff4920d94fc00b003e60cb942d4a to your computer and use it in GitHub Desktop.
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.
// 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