Last active
July 8, 2019 10:02
-
-
Save DevAndArtist/ed277c90d20731c3fda4718488b0f30f to your computer and use it in GitHub Desktop.
Applying SwiftUI ideas to UIKit
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
import RxSwift | |
import RxCocoa | |
import RxRelay | |
extension Disposable { | |
public func wrapIntoAnyDisposable() -> AnyDisposable { | |
return AnyDisposable(self) | |
} | |
} | |
public final class AnyDisposable: Disposable, Hashable { | |
private final class _DisposeBox { | |
let dispose: () -> Void | |
init(_ dispose: @escaping () -> Void) { | |
self.dispose = dispose | |
} | |
} | |
private let _disposeBox: _DisposeBox | |
public init(_ dispose: @escaping () -> Void) { | |
_disposeBox = _DisposeBox(dispose) | |
} | |
public init<D>(_ disposable: D) where D: Disposable { | |
_disposeBox = _DisposeBox { | |
disposable.dispose() | |
} | |
} | |
public func dispose() { | |
_disposeBox.dispose() | |
} | |
deinit { | |
dispose() | |
} | |
public static func == (lhs: AnyDisposable, rhs: AnyDisposable) -> Bool { | |
return lhs._disposeBox === rhs._disposeBox | |
} | |
public func hash(into hasher: inout Hasher) { | |
let identifier = ObjectIdentifier(_disposeBox) | |
hasher.combine(identifier) | |
} | |
public final func store<C>(in collection: inout C) | |
where | |
C: RangeReplaceableCollection, | |
C.Element == AnyDisposable | |
{ | |
collection.append(self) | |
} | |
public final func store(in set: inout Set<AnyDisposable>) { | |
set.insert(self) | |
} | |
public final func store<Key>( | |
in dictionary: inout Dictionary<Key, AnyDisposable>, | |
for key: Key | |
) where Key: Hashable { | |
dictionary[key] = self | |
} | |
} | |
public protocol __Identifiable { | |
associatedtype Identity: Hashable | |
var identity: Identity { get } | |
associatedtype IdentifiedValue = Self | |
var identifiedValue: IdentifiedValue { get } | |
} | |
extension __Identifiable where Self == IdentifiedValue { | |
public var identifiedValue: Self { | |
return self | |
} | |
} | |
public protocol UpdatableObject: AnyObject, __Identifiable { | |
associatedtype PublisherType: ObservableType | |
var didChange: PublisherType { get } | |
} | |
extension UpdatableObject where PublisherType.Element == Self { | |
public subscript<Subject>( | |
keyPath path: ReferenceWritableKeyPath<Self, Subject> | |
) -> Updating<Subject> { | |
// FIXME: Remove `return` in Swift 5.1. | |
return Updating( | |
getValue: { self[keyPath: path] }, | |
setValue: { newValue in | |
self[keyPath: path] = newValue | |
}, | |
observe: { | |
self.didChange.map { value in | |
value[keyPath: path] | |
} | |
} | |
) | |
} | |
} | |
//@propertyWrapper | |
public struct Updating<Value> { | |
private let _getter: () -> Value | |
private let _setter: (Value) -> Void | |
private let _observable: () -> Observable<Value> | |
public var wrappedValue: Value { | |
get { | |
// FIXME: Remove `return` in Swift 5.1. | |
return _getter() | |
} | |
nonmutating set { | |
_setter(newValue) | |
} | |
} | |
public var didChange: Observable<Value> { | |
// FIXME: Remove `return` in Swift 5.1. | |
return _observable() | |
} | |
public init( | |
getValue getter: @escaping () -> Value, | |
setValue setter: @escaping (Value) -> Void, | |
observe observable: @escaping () -> Observable<Value> | |
) { | |
_getter = getter | |
_setter = setter | |
_observable = observable | |
} | |
public static func constant(_ value: Value) -> Updating { | |
return Updating( | |
getValue: { value }, | |
setValue: { _ in | |
assertionFailure("cannot set a constant") | |
}, | |
observe: { | |
Observable<Value>.never() | |
} | |
) | |
} | |
public static func readOnly(_ value: Value) -> Updating { | |
return Updating( | |
getValue: { value }, | |
setValue: { _ in | |
print("API MISUSE: cannot set a read only value") | |
}, | |
observe: { | |
Observable<Value>.never() | |
} | |
) | |
} | |
} | |
//@dynamicMemberLookup | |
public protocol UpdatingConvertible { | |
associatedtype Value | |
var updating: Updating<Value> { get } | |
// subscript<Subject>( | |
// dynamicMember path: WritableKeyPath<Value, Subject> | |
// ) -> Updating<Subject> { get } | |
} | |
extension UpdatingConvertible { | |
public subscript<Subject>( | |
dynamicMember path: WritableKeyPath<Value, Subject> | |
) -> Updating<Subject> { | |
return Updating( | |
getValue: { | |
self.updating.wrappedValue[keyPath: path] | |
}, | |
setValue: { newValue in | |
self.updating.wrappedValue[keyPath: path] = newValue | |
}, | |
observe: { | |
self.updating.didChange.map { value in | |
value[keyPath: path] | |
} | |
} | |
) | |
} | |
} | |
extension Updating: UpdatingConvertible { | |
public var updating: Updating { | |
// FIXME: Remove `return` in Swift 5.1. | |
return self | |
} | |
} | |
@propertyWrapper | |
public struct ObjectUpdating<UpdatableObjectType> | |
where UpdatableObjectType: UpdatableObject | |
{ | |
public var wrappedValue: UpdatableObjectType | |
public var projectedValue: Wrapper { | |
// FIXME: Remove `return` in Swift 5.1. | |
return Wrapper(wrappedValue: wrappedValue) | |
} | |
public init(initialValue: UpdatableObjectType) { | |
wrappedValue = initialValue | |
} | |
} | |
extension ObjectUpdating { | |
// @dynamicMemberLookup | |
public struct Wrapper { | |
let wrappedValue: UpdatableObjectType | |
init(wrappedValue: UpdatableObjectType) { | |
self.wrappedValue = wrappedValue | |
} | |
} | |
} | |
extension ObjectUpdating.Wrapper | |
where UpdatableObjectType.PublisherType.Element == UpdatableObjectType | |
{ | |
public subscript<Subject>( | |
dynamicMember path: ReferenceWritableKeyPath<UpdatableObjectType, Subject> | |
) -> Updating<Subject> { | |
return wrappedValue[keyPath: path] | |
} | |
} | |
//@propertyWrapper | |
public struct Updatable<Value> { | |
public let relay: BehaviorRelay<Updating<Value>> | |
public var updating: Updating<Value> { | |
// FIXME: Remove `return` in Swift 5.1. | |
return relay.value | |
} | |
public var didChange: Observable<Value> { | |
// FIXME: Remove `return` in Swift 5.1. | |
return relay.flatMapLatest { updating in | |
updating.didChange | |
} | |
} | |
public var wrappedValue: Value { | |
get { | |
// FIXME: Remove `return` in Swift 5.1. | |
return updating.wrappedValue | |
} | |
set { | |
updating.wrappedValue = newValue | |
} | |
} | |
public var projectedValue: Binder { | |
// FIXME: Remove `return` in Swift 5.1. | |
return Binder(relay: relay) | |
} | |
public init(updating: Updating<Value>) { | |
relay = BehaviorRelay(value: updating) | |
} | |
public struct Binder { | |
let relay: BehaviorRelay<Updating<Value>> | |
public func bind( | |
_ observable: Observable<Updating<Value>> | |
) -> AnyDisposable { | |
// FIXME: Remove `return` in Swift 5.1. | |
return observable | |
.bind(to: relay) | |
.wrapIntoAnyDisposable() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment