Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

Commit

Permalink
Merge pull request #29 from hainayanda/second-rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
hainayanda authored Nov 24, 2022
2 parents c51a6fa + 11328be commit 454d8fe
Show file tree
Hide file tree
Showing 42 changed files with 1,333 additions and 1,312 deletions.
416 changes: 210 additions & 206 deletions Example/Pods/Pods.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

15 changes: 4 additions & 11 deletions Example/Tests/BasicTest/CombineSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class CombineSpec: QuickSpec {
}
it("should combine 2 subjects") {
let subject2 = Subject<Int>(wrappedValue: 0)
let changeWrapper = listenDidSet(for: subject.compactCombine(with: subject2))
let combined = subject.compactCombine(with: subject2)
let changeWrapper = listenDidSet(for: combined)
let newState1: String = .randomString(length: 18)
let newState2: Int = .random(in: -1000 ..< 1000)
subject.wrappedValue = newState1
Expand All @@ -45,7 +46,8 @@ class CombineSpec: QuickSpec {
it("should combine 3 subjects") {
let subject2 = Subject<Int>(wrappedValue: 0)
let subject3 = Subject<Bool>(wrappedValue: false)
let changeWrapper = listenDidSet(for: subject.compactCombine(with: subject2, subject3))
let combined = subject.compactCombine(with: subject2, subject3)
let changeWrapper = listenDidSet(for: combined)
let newState1: String = .randomString(length: 18)
let newState2: Int = .random(in: -1000 ..< 1000)
subject.wrappedValue = newState1
Expand Down Expand Up @@ -157,15 +159,6 @@ private func listenDidSet<State>(for subject: Observable<State>, retainUntilStat
return changeWrapper
}

private func listenDidSet<State: Equatable>(for subject: Observable<State>, retainUntilState changes: Changes<State>) -> ChangesClassWrapper<State> {
let changeWrapper = ChangesClassWrapper<State>()
subject
.observeChange { changes in
changeWrapper.changes = changes
}.retainUntil { $0 == changes }
return changeWrapper
}

// MARK: When

// MARK: Assertion
Expand Down
69 changes: 38 additions & 31 deletions Example/Tests/BasicTest/ObservableSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,42 @@ class ObservableSpec: QuickSpec {
setCount: 1
)
}
it("should fire to the one request it") {
var firstObserverCalled: Bool = false
var secondObserverCalled: Bool = false
var thirdObserverCalled: Bool = false
var fourthObserverCalled: Bool = false
subject.observe { _ in
firstObserverCalled = true
}.retain()

subject.filter { _ in true }.observe { _ in
secondObserverCalled = true
}.retain()

subject.observe { _ in
thirdObserverCalled = true
}
.retain()
.fire()

expect(firstObserverCalled).to(beFalse())
expect(secondObserverCalled).to(beFalse())
expect(thirdObserverCalled).to(beTrue())

thirdObserverCalled = false

subject.filter { _ in true }.observe { _ in
fourthObserverCalled = true
}
.retain()
.fire()

expect(firstObserverCalled).to(beFalse())
expect(secondObserverCalled).to(beFalse())
expect(thirdObserverCalled).to(beFalse())
expect(fourthObserverCalled).to(beTrue())
}
it("should retain subscriber using retainer") {
var retainer: Retainer? = Retainer()
let changeWrapper = listenDidSet(for: subject, retainTo: retainer!)
Expand Down Expand Up @@ -86,25 +122,6 @@ class ObservableSpec: QuickSpec {
setCount: 1
)
}
it("should retain subscriber until met condition") {
let newState: String = .randomString(length: 18)
let triggeringState: String = .randomString(length: 18)
let stopChanges = Changes(new: triggeringState, old: newState)
let expectedChanges = Changes(new: newState, old: intialState)
let changeWrapper = listenDidSet(for: subject, retainUntilState: stopChanges)
subject.wrappedValue = newState
thenAssert(
changeWrapper,
shouldSimilarWith: expectedChanges,
setCount: 1
)
subject.wrappedValue = triggeringState
thenAssert(
changeWrapper,
shouldSimilarWith: expectedChanges,
setCount: 1
)
}
it("should retain subscriber for given timeinterval") {
let newState: String = .randomString(length: 18)
let expectedChanges = Changes(new: newState, old: intialState)
Expand Down Expand Up @@ -155,7 +172,8 @@ class ObservableSpec: QuickSpec {
}
it("should merge subjects") {
let subject2 = Subject<String>(wrappedValue: intialState)
let changeWrapper = listenDidSet(for: subject.merged(with: subject2))
let merged = subject.merged(with: subject2)
let changeWrapper = listenDidSet(for: merged)
let newState1: String = .randomString(length: 18)
let newState2: String = .randomString(length: 18)
subject.wrappedValue = newState1
Expand Down Expand Up @@ -214,17 +232,6 @@ private func listenDidSet<State>(for subject: Observable<State>, retainUntilStat
return changeWrapper
}

private func listenDidSet<State: Equatable>(for subject: Observable<State>, retainUntilState changes: Changes<State>) -> ChangesClassWrapper<State> {
let changeWrapper = ChangesClassWrapper<State>()
subject
.observeChange { changes in
changeWrapper.changes = changes
}.retainUntil {
$0 == changes
}
return changeWrapper
}

private func listenDidSet<State>(for subject: Observable<State>, retainUntil timeInterval: TimeInterval) -> ChangesClassWrapper<State> {
let changeWrapper = ChangesClassWrapper<State>()
subject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ class UICollectionViewRelayCollectionSpec: QuickSpec {
}
}

extension UICollectionViewRelayCollectionSpec:
extension UICollectionViewRelayCollectionSpec :
UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDataSourcePrefetching {

func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ class UITableViewRelayCollectionSpec: QuickSpec {
}
}

extension UITableViewRelayCollectionSpec:
extension UITableViewRelayCollectionSpec :
UITableViewDelegate, UITableViewDataSource, UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
2
Expand Down
2 changes: 1 addition & 1 deletion Pharos.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Pod::Spec.new do |s|
s.name = 'Pharos'
s.version = '3.0.2'
s.version = '4.0.0'
s.summary = 'Pharos is Observer pattern framework for Swift that utilize `propertyWrapper'

# This description is used to generate tags and improve search results.
Expand Down
33 changes: 33 additions & 0 deletions Pharos/Classes/Base/AnyObservable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// AnyObservable.swift
// Pharos
//
// Created by Nayanda on 16/11/22.
//

import Foundation

// MARK: AnyObservable

public protocol AnyObservable: AnyObject {
var isAncestor: Bool { get }
var parent: AnyObservable? { get }
var source: AnyObservable? { get }
var isValid: Bool { get }
func release()
func typeErased() -> Observable<Any>
}

typealias InvokableObservable = AnyObservable & InvokeChainable

// MARK: AnyObservable + Extensions

extension AnyObservable {
@inlinable public var ancestor: AnyObservable? {
isAncestor ? self: parent?.ancestor
}

@inlinable public var source: AnyObservable? { ancestor }

@inlinable public var isValid: Bool { ancestor != nil }
}
85 changes: 85 additions & 0 deletions Pharos/Classes/Base/Changes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//
// Changes.swift
// Pharos
//
// Created by Nayanda Haberty on 15/04/21.
//

import Foundation

public struct Changes<State> {
public let new: State
public internal(set) var old: State?
var consumers: [ObjectIdentifier] = []
let triggers: [ObjectIdentifier]?

public init(new: State, old: State? = nil, triggers: [ObjectIdentifier]? = nil, consumers: [ObjectIdentifier] = []) {
self.triggers = triggers
self.new = new
self.old = old
self.consumers = consumers
}

func canBeConsumed(by source: AnyObject) -> Bool {
guard !alreadyConsumed(by: source) else { return false }
guard let triggers = triggers else { return true }
let realSource = (source as? WrappingObserver)?.wrapped ?? source
guard triggers.contains(ObjectIdentifier(realSource)) else {
guard let parent = (realSource as? AnyObservable)?.parent else { return false }
return triggers.contains(ObjectIdentifier(parent))
}
return true
}

func alreadyConsumed(by source: AnyObject) -> Bool {
consumers.contains(ObjectIdentifier(source))
}

func consumed(by source: AnyObject) -> Changes {
var mutableSelf = self
mutableSelf.consumers.append(ObjectIdentifier(source))
return mutableSelf
}

func with(old: State?) -> Changes {
var mutableSelf = self
mutableSelf.old = old
return mutableSelf
}

func mapped<NewState>(_ mapper: (State) -> NewState) -> Changes<NewState> {
var newChanges = Changes<NewState>(new: mapper(new))
newChanges.consumers = consumers
guard let old = self.old else { return newChanges }
return newChanges.with(old: mapper(old))
}

func compactMapped<NewState>(_ mapper: (State) -> NewState?) -> Changes<NewState>? {
guard let newMapped = mapper(new) else { return nil }
var newChanges = Changes<NewState>(new: newMapped)
newChanges.consumers = consumers
guard let old = self.old else { return newChanges }
return newChanges.with(old: mapper(old))
}
}

extension Changes: Equatable where State: Equatable {
@inlinable public var isChanging: Bool { !isNotChanging }
@inlinable public var isNotChanging: Bool { new == old }

@inlinable public static func == (lhs: Changes<State>, rhs: Changes<State>) -> Bool {
lhs.new == rhs.new && lhs.old == rhs.old
}
}

extension Changes: Hashable where State: Hashable {
@inlinable public func hash(into hasher: inout Hasher) {
hasher.combine(new)
hasher.combine(old)
}
}

extension Changes where State: AnyObject {
@inlinable public var isSameInstance: Bool { new === old }
@inlinable public var isNewInstance: Bool { !isSameInstance }
}
25 changes: 25 additions & 0 deletions Pharos/Classes/Base/Invokable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// Invokable.swift
// Pharos
//
// Created by Nayanda Haberty on 5/10/22.
//

import Foundation

public protocol Invokable: AnyObject {
func fire()
}

protocol InvokeChainable: Invokable {
func signalFire(from triggers: [ObjectIdentifier])
}

extension Invokable {
@inlinable func fire(after delay: TimeInterval) {
(DispatchQueue.current ?? DispatchQueue.main)
.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.fire()
}
}
}
21 changes: 21 additions & 0 deletions Pharos/Classes/Base/InvokableGroup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// InvokableGroup.swift
// Pharos
//
// Created by Nayanda Haberty on 24/11/22.
//

import Foundation

class InvokableGroup: Invokable {

let invokables: [Invokable]

init(invokables: [Invokable]) {
self.invokables = invokables
}

@inlinable func fire() {
invokables.first?.fire()
}
}
17 changes: 17 additions & 0 deletions Pharos/Classes/Base/Observing.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Observing.swift
// Pharos
//
// Created by Nayanda on 16/11/22.
//

import Foundation

// MARK: Observing

protocol Observing {
associatedtype Input

@discardableResult
func accept(_ changes: Changes<Input>) -> Bool
}
34 changes: 34 additions & 0 deletions Pharos/Classes/Base/Unwrapable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// Unwrapable.swift
// Pharos
//
// Created by Nayanda Haberty on 16/11/22.
//

import Foundation

// MARK: Unwrappable

public protocol Unwrapable {
associatedtype Wrapped

func unwrap() -> Wrapped?
}

// MARK: Optional + Unwrapable

extension Optional: Unwrapable {
@inlinable public func unwrap() -> Wrapped? {
self
}
}

// MARK: Observable + Unwrappable AKA Optional

extension Observable where Output: Unwrapable {
/// Ignore all nil from this Observable
/// - Returns: New Observable that ignores nil
@inlinable public func compacted() -> Observable<Output.Wrapped> {
compactMapped { $0.unwrap() }
}
}
Loading

0 comments on commit 454d8fe

Please sign in to comment.