Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//
// Observable Object Publisher.swift
// PublisherKit
//
// Created by Raghav Ahuja on 29/03/20.
//

/// The default publisher of an `ObservableObject`.
final public class ObservableObjectPublisher: Publisher {

public typealias Output = Void

public typealias Failure = Never

private let lock = Lock()

private var connections = Set<Conduit>()

public init() { }

final public func receive<S: Subscriber>(subscriber: S) where Output == S.Input, Failure == S.Failure {
let inner = Inner(downstream: subscriber, parent: self)

lock.lock()
connections.insert(inner)
lock.unlock()

subscriber.receive(subscription: inner)
}

final public func send() {
lock.lock()
let connections = self.connections
lock.unlock()

connections.forEach { (connection) in
connection.send()
}
}

private func remove(_ conduit: Conduit) {
lock.lock()
connections.remove(conduit)
lock.unlock()
}
}

extension ObservableObjectPublisher {

private class Conduit: Hashable {

func send() {
/* abstract method to be overrided by Inner subclass. */
}

static func == (lhs: Conduit, rhs: Conduit) -> Bool {
return lhs === rhs
}

func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}

private final class Inner<Downstream: Subscriber>: Conduit, Subscription, CustomStringConvertible, CustomPlaygroundDisplayConvertible, CustomReflectable where Output == Downstream.Input, Failure == Downstream.Failure {

private let downstream: Downstream
private var parent: ObservableObjectPublisher?

private let lock = Lock()
private let downstreamLock = RecursiveLock()

private enum State {
case awaiting
case subscribed
case terminated
}

private var state: State = .awaiting

init(downstream: Downstream, parent: ObservableObjectPublisher) {
self.downstream = downstream
self.parent = parent
}

override func send() {
lock.lock()
let state = self.state
lock.unlock()

guard state == .subscribed else { return }

downstreamLock.lock()
_ = downstream.receive()
downstreamLock.unlock()
}

func request(_ demand: Subscribers.Demand) {
lock.lock()
guard state == .awaiting else { lock.unlock(); return }
state = .subscribed
lock.unlock()
}

func cancel() {
lock.lock()
state = .terminated
lock.unlock()

parent?.remove(self)
parent = nil
}

var description: String {
"ObservableObjectPublisher"
}

var playgroundDescription: Any {
description
}

var customMirror: Mirror {
let children: [Mirror.Child] = [
("downstream", downstream)
]

return Mirror(self, children: children)
}
}
}
49 changes: 49 additions & 0 deletions Sources/PublisherKit/Observable Object/Observable Object.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// Observable Object.swift
// PublisherKit
//
// Created by Raghav Ahuja on 29/03/20.
//

/// A type of object with a publisher that emits before the object has changed.
///
/// By default an `ObservableObject` will synthesize an `objectWillChange`
/// publisher that emits before any of its `@Published` properties changes:
///
/// class Contact: ObservableObject {
/// @Published var name: String
/// @Published var age: Int
///
/// init(name: String, age: Int) {
/// self.name = name
/// self.age = age
/// }
///
/// func haveBirthday() -> Int {
/// age += 1
/// return age
/// }
/// }
///
/// let john = Contact(name: "John Appleseed", age: 24)
/// john.objectWillChange.sink { _ in print("\(john.age) will change") }
/// print(john.haveBirthday())
/// // Prints "24 will change"
/// // Prints "25"
///
public protocol ObservableObject: AnyObject {

/// The type of publisher that emits before the object has changed.
associatedtype ObjectWillChangePublisher: Publisher = ObservableObjectPublisher where ObjectWillChangePublisher.Failure == Never

/// A publisher that emits before the object has changed.
var objectWillChange: ObjectWillChangePublisher { get }
}

extension ObservableObject where ObjectWillChangePublisher == ObservableObjectPublisher {

/// A publisher that emits before the object has changed.
public var objectWillChange: ObservableObjectPublisher {
ObservableObjectPublisher()
}
}
75 changes: 75 additions & 0 deletions Sources/PublisherKit/Observable Object/Published.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// Published.swift
// PublisherKit
//
// Created by Raghav Ahuja on 29/03/20.
//

/// Adds a `Publisher` to a property.
///
/// Properties annotated with `@Published` contain both the stored value and a publisher which sends any new values after the property value has been sent. New subscribers will receive the current value of the property first.
/// Note that the `@Published` property is class-constrained. Use it with properties of classes, not with non-class types like structures.
@propertyWrapper public struct Published<Value> {

private var value: Value

@available(*, unavailable, message: "@Published is only available on properties of classes")
public var wrappedValue: Value {
get { value }
set { value = newValue }
}

private var publisher: Publisher?
var objectWillChange: ObservableObjectPublisher?

/// Initialize the storage of the Published property as well as the corresponding `Publisher`.
public init(initialValue: Value) {
value = initialValue
}

public init(wrappedValue: Value) {
value = wrappedValue
}

/// A publisher for properties marked with the `@Published` attribute.
public struct Publisher: PublisherKit.Publisher {

public typealias Output = Value

public typealias Failure = Never

fileprivate let subject: CurrentValueSubject<Value, Never>

fileprivate init(_ output: Output) {
subject = CurrentValueSubject(output)
}

public func receive<S: Subscriber>(subscriber: S) where Output == S.Input, Failure == S.Failure {
subject.subscribe(subscriber)
}
}

/// The property that can be accessed with the `$` syntax and allows access to the `Publisher`
public var projectedValue: Publisher {
mutating get {
if let publisher = publisher {
return publisher
}

let publisher = Publisher(value)
self.publisher = publisher

return publisher
}
}

public static subscript<EnclosingSelf: AnyObject>(_enclosingInstance object: EnclosingSelf, wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>, storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Published<Value>>) -> Value {
get {
object[keyPath: storageKeyPath].value
} set {
object[keyPath: storageKeyPath].objectWillChange?.send()
object[keyPath: storageKeyPath].publisher?.subject.send(newValue)
object[keyPath: storageKeyPath].value = newValue
}
}
}