Skip to content

Instantly share code, notes, and snippets.

@shawnthroop
Last active July 2, 2018 14:35
Show Gist options
  • Save shawnthroop/d6be9b81a40a27c44a19e631290a52ed to your computer and use it in GitHub Desktop.
Save shawnthroop/d6be9b81a40a27c44a19e631290a52ed to your computer and use it in GitHub Desktop.
An elaboration of Swift Talk S01E63 (Mutable Shared Structs). Provides a simpler base class for read-only properties.
// swift 4.1
import Foundation
import Sting
struct Account: Equatable {
var id: Int
}
struct Accounts {
private var content: MutableVariable<[Account]>
private var disposables = [Any]()
init(accounts: [Account]) {
self.content = MutableVariable(accounts)
disposables.append(content[\.first].addChangeObserver { newValue, oldValue in
print("Current Account: \(String(describing: newValue?.id)), old: \(String(describing: oldValue?.id))")
})
}
init(_ accounts: Account...) {
self.init(accounts: accounts)
}
var current: Account? {
return content.value.first
}
mutating func prepend(_ account: Account) {
content.value.insert(account, at: content.value.startIndex)
}
mutating func remove(_ account: Account) {
if let i = content.value.index(of: account) {
content.value.remove(at: i)
}
}
}
let a0 = Account(id: 0)
let a1 = Account(id: 1)
let a2 = Account(id: 2)
var accounts = Accounts(a0, a1)
accounts.prepend(a2)
// Current Account: Optional(2), old: Optional(0)
accounts.remove(a2)
// Current Account: Optional(0), old: Optional(2)
accounts.remove(a1) // nothing is triggered! 👍🏼
accounts.remove(a0)
// Current Account: nil, old: Optional(0)
// swift 4.1
import Foundation
extension DispatchQueue {
public static func concurrent(_ label: String) -> DispatchQueue {
return DispatchQueue(label: label, attributes: .concurrent)
}
}
// swift 4.1
public final class Disposable {
private let dispose: () -> Void
public init(_ dispose: @escaping () -> Void) {
self.dispose = dispose
}
deinit {
dispose()
}
}
// swift 4.1
import Foundation
public class MutableVariable<T>: Variable<T> {
internal typealias Setter = (T) -> Void
private let _setter: Setter
public convenience init(_ initial: T, isThreadSafe threadSafe: Bool = false) {
self.init(initial, queue: threadSafe == true ? .concurrent("com.shawnthroop.sting.MutableVariable.queue") : nil)
}
private convenience init(_ initial: T, queue: DispatchQueue?) {
var (observers, keys) = MutableVariable.observersAndKeys()
var value: T = initial {
didSet {
for observer in observers.values {
observer(value, oldValue)
}
}
}
self.init(queue: queue, get: { value }, set: { newValue in value = newValue }) { observer in
let key = keys.next()!
observers[key] = observer
return Disposable {
observers[key] = nil
}
}
}
internal init(queue: DispatchQueue?, get: @escaping Getter, set: @escaping Setter, addObserver: @escaping AddObserver) {
if let queue = queue {
_setter = { newValue in
queue.async(flags: .barrier) {
set(newValue)
}
}
} else {
_setter = set
}
super.init(queue: queue, get: get, addObserver: addObserver)
}
public override var value: T {
get {
return super.value
}
set {
_setter(newValue)
}
}
public func performBatchMutation(_ mutation: (inout T) -> Void) {
mutation(&value)
}
public subscript<V>(keyPath: WritableKeyPath<T,V>) -> MutableVariable<V> {
return MutableVariable<V>(queue: nil, get: { self.value[keyPath: keyPath] }, set: { newValue in
self.value[keyPath: keyPath] = newValue
}, addObserver: { observer in
self.addObserver { newValue, oldValue in
observer(newValue[keyPath: keyPath], oldValue[keyPath: keyPath])
}
})
}
}
extension MutableVariable where T: MutableCollection {
public subscript(index: T.Index) -> MutableVariable<T.Element> {
return MutableVariable<T.Element>(queue: nil, get: { self.value[index] }, set: { newValue in self.value[index] = newValue }) { observer in
self.addObserver { newValue, oldValue in
observer(newValue[index], oldValue[index])
}
}
}
}
// swift 4.1
import Foundation
/// A reference type that allows observing value types.
///
/// - Note:
/// Orignal concept from [Swift Talk - S01E63: Mutable Shared Structs](https://talk.objc.io/episodes/S01E63-mutable-shared-structs-part-2)
public class Variable<T> {
public typealias Observer = (_ newValue: T, _ oldValue: T) -> Void
public typealias ObservationPredicate = (_ newValue: T, _ oldValue: T) -> Bool
internal typealias Getter = () -> T
internal typealias AddObserver = (@escaping Observer) -> Disposable
private let _getter: Getter
private let _addObserver: AddObserver
public convenience init(_ initial: T, isThreadSafe threadSafe: Bool = false) {
self.init(initial, queue: threadSafe == true ? .concurrent("com.shawnthroop.sting.Variable.queue") : nil)
}
private convenience init(_ initial: T, queue: DispatchQueue?) {
var (observers, keys) = Variable.observersAndKeys()
var value: T = initial {
didSet {
for observer in observers.values {
observer(value, oldValue)
}
}
}
self.init(queue: queue, get: { value }) { observer in
let key = keys.next()!
observers[key] = observer
return Disposable {
observers[key] = nil
}
}
}
internal init(queue: DispatchQueue?, get: @escaping Getter, addObserver: @escaping AddObserver) {
if let queue = queue {
_getter = {
return queue.sync { get() }
}
_addObserver = { observer in
return queue.sync { addObserver(observer) }
}
} else {
_getter = get
_addObserver = addObserver
}
}
public var value: T {
get {
return _getter()
}
}
public func addObserver(_ observer: @escaping Observer) -> Disposable {
return _addObserver(observer)
}
public func addObserver(where predicate: @escaping ObservationPredicate, observer: @escaping Observer) -> Disposable {
return addObserver { newValue, oldValue in
guard predicate(newValue, oldValue) else {
return
}
observer(newValue, oldValue)
}
}
public subscript<V>(keyPath: KeyPath<T,V>) -> Variable<V> {
return Variable<V>(queue: nil, get: { self.value[keyPath: keyPath] }) { observer in
self.addObserver { newValue, oldValue in
observer(newValue[keyPath: keyPath], oldValue[keyPath: keyPath])
}
}
}
}
extension Variable where T: Equatable {
public func addChangeObserver(_ observer: @escaping Observer) -> Disposable {
return addObserver(where: !=, observer: observer)
}
}
internal extension Variable {
static func observersAndKeys() -> (observers: [Int: Observer], keys: CountablePartialRangeFrom<Int>.Iterator) {
return ([Int: Observer](), (0...).makeIterator())
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment