Skip to content

Commit

Permalink
Split subscription logic.
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio committed May 28, 2020
1 parent e4d65d7 commit 2a29ae7
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 37 deletions.
8 changes: 8 additions & 0 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
5B8F922124732C4600C1C90E /* SwiftUIBasicBindingHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F922024732C4600C1C90E /* SwiftUIBasicBindingHomeView.swift */; };
5B8F922324732C9500C1C90E /* LoopBindingExampleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F922224732C9500C1C90E /* LoopBindingExampleView.swift */; };
5B8F922724732E1700C1C90E /* SimpleCounterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B8F922624732E1700C1C90E /* SimpleCounterView.swift */; };
5BAB9750247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAB974F247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift */; };
5BAB9751247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAB974F247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift */; };
5BAB9752247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BAB974F247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift */; };
5BC88F842469CBA300394C63 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BC88F832469CBA300394C63 /* Nimble.framework */; };
5BC88F862469CBAB00394C63 /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5BC88F852469CBAB00394C63 /* Nimble.framework */; };
5BC88F88246AFC5900394C63 /* ReactiveSwift+EnqueueTo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC88F87246AFC5900394C63 /* ReactiveSwift+EnqueueTo.swift */; };
Expand Down Expand Up @@ -228,6 +231,7 @@
5B8F922224732C9500C1C90E /* LoopBindingExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopBindingExampleView.swift; sourceTree = "<group>"; };
5B8F922424732CA000C1C90E /* EnvironmentLoopExampleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentLoopExampleView.swift; sourceTree = "<group>"; };
5B8F922624732E1700C1C90E /* SimpleCounterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleCounterView.swift; sourceTree = "<group>"; };
5BAB974F247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIHotSwappableSubscription.swift; sourceTree = "<group>"; };
5BC88F832469CBA300394C63 /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5BC88F852469CBAB00394C63 /* Nimble.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Nimble.framework; sourceTree = BUILT_PRODUCTS_DIR; };
5BC88F87246AFC5900394C63 /* ReactiveSwift+EnqueueTo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReactiveSwift+EnqueueTo.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -468,6 +472,7 @@
5B8F92132473250E00C1C90E /* EnvironmentLoop.swift */,
5B8F921B247325C300C1C90E /* EnvironmentValues.swift */,
5B8F920F2473242900C1C90E /* SwiftUISubscription.swift */,
5BAB974F247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
Expand Down Expand Up @@ -815,6 +820,7 @@
65761B2E23CF4CA2004D5506 /* NSLock+Extensions.swift in Sources */,
5BC88F9C246B1CDE00394C63 /* SignalProducer+Loop.swift in Sources */,
5BC88F92246B17B200394C63 /* Context.swift in Sources */,
5BAB9750247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift in Sources */,
585CD87B239E6A39004BE9CC /* Reducer.swift in Sources */,
9AD5D42D1F97375E00E6AE5A /* Property+System.swift in Sources */,
5BC88F88246AFC5900394C63 /* ReactiveSwift+EnqueueTo.swift in Sources */,
Expand Down Expand Up @@ -869,6 +875,7 @@
65761B2F23CF4CA2004D5506 /* NSLock+Extensions.swift in Sources */,
5BC88F9D246B1CDE00394C63 /* SignalProducer+Loop.swift in Sources */,
5BC88F93246B17B200394C63 /* Context.swift in Sources */,
5BAB9751247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift in Sources */,
585CD87C239E6A3E004BE9CC /* Reducer.swift in Sources */,
65F8C262218371A800924657 /* Property+System.swift in Sources */,
5BC88F89246AFC5900394C63 /* ReactiveSwift+EnqueueTo.swift in Sources */,
Expand All @@ -893,6 +900,7 @@
65761B3023CF4CA2004D5506 /* NSLock+Extensions.swift in Sources */,
5BC88F9E246B1CDE00394C63 /* SignalProducer+Loop.swift in Sources */,
5BC88F94246B17B200394C63 /* Context.swift in Sources */,
5BAB9752247FFBC10079B532 /* SwiftUIHotSwappableSubscription.swift in Sources */,
585CD87D239E6A3E004BE9CC /* Reducer.swift in Sources */,
65F8C271218371AC00924657 /* Property+System.swift in Sources */,
5BC88F8A246AFC5900394C63 /* ReactiveSwift+EnqueueTo.swift in Sources */,
Expand Down
22 changes: 7 additions & 15 deletions Loop/Public/SwiftUI/EnvironmentLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import ReactiveSwift
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
@propertyWrapper
public struct EnvironmentLoop<State, Event>: DynamicProperty {
@Environment(\.loops[ObjectIdentifier(Loop<State, Event>.self)])
var erasedLoop: Any?
@Environment(\.loops[LoopType(Loop<State, Event>.self)])
var erasedLoop: AnyObject?

@ObservedObject
private var subscription: SwiftUISubscription<State, Event>
private var subscription: SwiftUIHotSwappableSubscription<State, Event>

@inlinable
public var wrappedValue: State {
Expand All @@ -30,23 +30,15 @@ public struct EnvironmentLoop<State, Event>: DynamicProperty {
internal var acknowledgedState: State!

public init() {
self.subscription = SwiftUISubscription()
self.subscription = SwiftUIHotSwappableSubscription()
}

public mutating func update() {
if isKnownUniquelyReferenced(&subscription) == false {
subscription = SwiftUISubscription()
}

if subscription.hasStarted == false {
guard let loop = erasedLoop as! Loop<State, Event>? else {
fatalError("Expect parent view to inject a `Loop<\(State.self), \(Event.self)>` through `View.environmentLoop(_:)`. Found none.")
}

subscription.attach(to: loop)
guard let loop = erasedLoop as! Loop<State, Event>? else {
fatalError("Expect parent view to inject a `Loop<\(State.self), \(Event.self)>` through `View.environmentLoop(_:)`. Found none.")
}

acknowledgedState = subscription.latestValue
acknowledgedState = subscription.currentState(in: loop)
}
}

Expand Down
17 changes: 14 additions & 3 deletions Loop/Public/SwiftUI/EnvironmentValues.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import SwiftUI
extension View {
@inlinable
public func environmentLoop<State, Event>(_ loop: Loop<State, Event>) -> some View {
let typeId = ObjectIdentifier(type(of: loop))
let typeId = LoopType(type(of: loop))

return transformEnvironment(\.loops) { loops in
loops[typeId] = loop
Expand All @@ -16,17 +16,28 @@ extension View {

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
extension EnvironmentValues {
public var loops: [ObjectIdentifier: Any] {
@usableFromInline
internal var loops: [LoopType: AnyObject] {
get { self[LoopEnvironmentKey.self] }
set { self[LoopEnvironmentKey.self] = newValue }
}
}

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
internal enum LoopEnvironmentKey: EnvironmentKey {
static var defaultValue: [ObjectIdentifier: Any] {
static var defaultValue: [LoopType: AnyObject] {
return [:]
}
}

@usableFromInline
struct LoopType: Hashable {
let id: ObjectIdentifier

@usableFromInline
init(_ type: Any.Type) {
id = ObjectIdentifier(type)
}
}

#endif
12 changes: 6 additions & 6 deletions Loop/Public/SwiftUI/LoopBinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,19 @@ public struct LoopBinding<State, Event>: DynamicProperty {
}

@usableFromInline
internal var acknowledgedState: State!
internal var acknowledgedState: State

public init(_ loop: Loop<State, Event>) {
// The subscription can be copied without restrictions.
self.subscription = SwiftUISubscription()
let subscription = SwiftUISubscription(loop: loop)

self.subscription = subscription
self.acknowledgedState = subscription.latestValue
self.loop = loop
}

public mutating func update() {
if subscription.hasStarted == false {
subscription.attach(to: loop)
}

// Move latest value from the subscription only when SwiftUI has requested an update.
acknowledgedState = subscription.latestValue
}

Expand Down
37 changes: 37 additions & 0 deletions Loop/Public/SwiftUI/SwiftUIHotSwappableSubscription.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#if canImport(Combine)

import Combine
import ReactiveSwift

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
internal final class SwiftUIHotSwappableSubscription<State, Event>: ObservableObject {

@Published private var latestValue: State!
private weak var attachedLoop: Loop<State, Event>?
private var disposable: Disposable?

init() {}

deinit {
disposable?.dispose()
}

func currentState(in loop: Loop<State, Event>) -> State {
if attachedLoop !== loop {
disposable?.dispose()

latestValue = loop.box._current

disposable = loop.producer
.observe(on: UIScheduler())
.startWithValues { [weak self] state in
guard let self = self else { return }
self.latestValue = state
}
}

return latestValue
}
}

#endif
19 changes: 6 additions & 13 deletions Loop/Public/SwiftUI/SwiftUISubscription.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import SwiftUI
#if canImport(Combine)

import Combine
import ReactiveSwift

@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
internal final class SwiftUISubscription<State, Event>: ObservableObject {
@Published var latestValue: State!
private(set) var hasStarted = false

@Published var latestValue: State
private var disposable: Disposable?

init() {}

deinit {
disposable?.dispose()
}

func attach(to loop: Loop<State, Event>) {
guard hasStarted == false else { return }
hasStarted = true

init(loop: Loop<State, Event>) {
latestValue = loop.box._current
disposable = loop.producer
.observe(on: UIScheduler())
Expand All @@ -29,6 +18,10 @@ internal final class SwiftUISubscription<State, Event>: ObservableObject {
self.latestValue = state
}
}

deinit {
disposable?.dispose()
}
}

#endif

0 comments on commit 2a29ae7

Please sign in to comment.