Skip to content

Instantly share code, notes, and snippets.

@DevAndArtist
Last active July 8, 2019 10:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DevAndArtist/ed277c90d20731c3fda4718488b0f30f to your computer and use it in GitHub Desktop.
Save DevAndArtist/ed277c90d20731c3fda4718488b0f30f to your computer and use it in GitHub Desktop.
Applying SwiftUI ideas to UIKit
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