Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Removing SubscribeSessionFactory and SubscriptionConfiguration #162

Merged
merged 15 commits into from
Mar 28, 2024
Merged
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
19 changes: 16 additions & 3 deletions .swiftformat
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
# format options
# Format options
--indent 2
--commas inline

--disable wrapMultilineStatementBraces
--wraparguments before-first
--wrapparameters before-first
--maxwidth 130
--disable wrapSingleLineComments
--voidtype void
--redundanttype inferred
--self remove

# file options
--enable spaceAroundBrackets,spaceAroundComments,spaceAroundGenerics,spaceAroundParens,spaceInsideBraces
--enable spaceInsideBrackets,spaceInsideComments,spaceInsideGenerics,spaceInsideParens,trailingclosures,trailingCommas
--enable wrapConditionalBodies, wrapEnumCases, wrapLoopBodies
--enable redundantLetError,redundantParens,redundantSelf,redundantReturn,redundantBreak
--enable duplicateImports

# File options
--exclude .build

# Swift Version
--swiftversion 5.0
--swiftversion 5.8
2 changes: 2 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
line_length:
warning: 130
ignores_comments: true
disabled_rules:
- identifier_name
Expand All @@ -11,6 +12,7 @@ excluded:
- .bundle
- fastlane
- Tests
- Pods
opt_in_rules:
- force_unwrapping
- overridden_super_call
Expand Down
64 changes: 34 additions & 30 deletions PubNub.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

338 changes: 338 additions & 0 deletions Sources/PubNub/DependencyContainer/DependencyContainer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
//
// DependencyContainer.swift
//
// Copyright (c) PubNub Inc.
// All rights reserved.
//
// This source code is licensed under the license found in the
// LICENSE file in the root directory of this source tree.
//

import Foundation

// A protocol that represents a unique key for each dependency. Each type conforming to `DependencyKey`
// represents a distinct dependency.
protocol DependencyKey {
// A value associated with a given `DependencyKey`
associatedtype Value
// Creates a value of the type Value for a given `DependencyKey` if no existing dependency
// is found in the `DependencyContainer`. The `container` parameter is used in case of
// nested dependencies, i.e., when the dependency being created depends on other objects in the `DependencyContainer`.
static func value(from container: DependencyContainer) -> Value
}

// The class that serves as a registry for dependencies. Each dependency is associated with a unique key
// conforming to the `DependencyKey` protocol.
class DependencyContainer {
private var resolvedValues: [ObjectIdentifier: any Wrappable] = [:]
private var registeredKeys: [ObjectIdentifier: (key: any DependencyKey.Type, scope: Scope)] = [:]

// Defines the lifecycle of the given dependency
enum Scope {
// The dependency is owned by the container. It lives as long as the container itself lives.
// The dependency is strongly referenced by the container.
case container
// The container does not own the dependency. The dependency could be deallocated even if the container
// is still alive, if there are no more strong references to it.
case weak
// Indicates that the DependencyContainer doesn't keep any reference (neither strong nor weak) to the dependency.
// Each time the dependency is requested, a new instance is created and returned
case transient
}

init(instanceID: UUID = UUID(), configuration: PubNubConfiguration) {
register(value: configuration, forKey: PubNubConfigurationDependencyKey.self)
register(value: instanceID, forKey: PubNubInstanceIDDependencyKey.self)
register(key: FileURLSessionDependencyKey.self, scope: .weak)
register(key: DefaultHTTPSessionDependencyKey.self, scope: .weak)
register(key: HTTPSubscribeSessionDependencyKey.self, scope: .weak)
register(key: HTTPPresenceSessionDependencyKey.self, scope: .weak)
register(key: HTTPSubscribeSessionQueueDependencyKey.self, scope: .weak)
register(key: PresenceStateContainerDependencyKey.self, scope: .weak)
register(key: SubscribeEventEngineDependencyKey.self, scope: .weak)
register(key: PresenceEventEngineDependencyKey.self, scope: .weak)
register(key: SubscriptionSessionDependencyKey.self, scope: .weak)
}

subscript<K>(key: K.Type) -> K.Value where K: DependencyKey {
guard let underlyingKey = registeredKeys[ObjectIdentifier(key)] else {
preconditionFailure("Cannot find \(key). Ensure this key was registered before")
}
if underlyingKey.scope == .transient {
if let value = underlyingKey.key.value(from: self) as? K.Value {
return value
} else {
preconditionFailure("Cannot create value for key \(key)")
}
}
if let valueWrapper = resolvedValues[ObjectIdentifier(key)] {
if let underlyingValue = valueWrapper.value as? K.Value {
return underlyingValue
}
}
if let value = underlyingKey.key.value(from: self) as? K.Value {
if Mirror(reflecting: value).displayStyle == .class && underlyingKey.scope == .weak {
resolvedValues[ObjectIdentifier(key)] = WeakWrapper(value as AnyObject)
} else {
resolvedValues[ObjectIdentifier(key)] = ValueWrapper(value)
}
return value
}
preconditionFailure("Cannot create value for key \(key)")
}

func register<K: DependencyKey>(key: K.Type, scope: Scope = .container) {
registeredKeys[ObjectIdentifier(key)] = (key: key, scope: scope)
}

@discardableResult
func register<K: DependencyKey>(value: K.Value?, forKey key: K.Type, in scope: Scope = .container) -> DependencyContainer {
guard let value = value else {
return self
}
registeredKeys[ObjectIdentifier(key)] = (key: key, scope: scope)

if Mirror(reflecting: value).displayStyle == .class && scope == .weak {
resolvedValues[ObjectIdentifier(key)] = WeakWrapper(value as AnyObject)
} else {
resolvedValues[ObjectIdentifier(key)] = ValueWrapper(value)
}

return self
}
}

typealias SubscribeEngine = EventEngine<any SubscribeState, Subscribe.Event, Subscribe.Invocation, Subscribe.Dependencies>
typealias PresenceEngine = EventEngine<any PresenceState, Presence.Event, Presence.Invocation, Presence.Dependencies>

extension DependencyContainer {
var configuration: PubNubConfiguration {
self[PubNubConfigurationDependencyKey.self]
}

var instanceID: UUID {
self[PubNubInstanceIDDependencyKey.self]
}

var fileURLSession: URLSessionReplaceable {
self[FileURLSessionDependencyKey.self]
}

var subscriptionSession: SubscriptionSession {
self[SubscriptionSessionDependencyKey.self]
}

var presenceStateContainer: PubNubPresenceStateContainer {
self[PresenceStateContainerDependencyKey.self]
}

var defaultHTTPSession: SessionReplaceable {
resolveSession(
session: self[DefaultHTTPSessionDependencyKey.self],
with: [automaticRetry].compactMap { $0 }
)
}

fileprivate var httpSubscribeSession: SessionReplaceable {
resolveSession(
session: self[HTTPSubscribeSessionDependencyKey.self],
with: [instanceIDOperator].compactMap { $0 }
)
}

fileprivate var httpPresenceSession: SessionReplaceable {
resolveSession(
session: self[HTTPPresenceSessionDependencyKey.self],
with: [instanceIDOperator].compactMap { $0 }
)
}

fileprivate var automaticRetry: RequestOperator? {
configuration.automaticRetry
}

fileprivate var instanceIDOperator: RequestOperator? {
configuration.useInstanceId ? InstanceIdOperator(instanceID: instanceID.uuidString) : nil
}

fileprivate var httpSubscribeSessionQueue: DispatchQueue {
self[HTTPSubscribeSessionQueueDependencyKey.self]
}

fileprivate var subscribeEngine: SubscribeEngine {
self[SubscribeEventEngineDependencyKey.self]
}

fileprivate var presenceEngine: PresenceEngine {
self[PresenceEventEngineDependencyKey.self]
}
}

private extension DependencyContainer {
func resolveSession(session: SessionReplaceable, with operators: [RequestOperator]) -> SessionReplaceable {
session.defaultRequestOperator == nil ? session.usingDefault(requestOperator: MultiplexRequestOperator(
operators: operators
)) : session.usingDefault(requestOperator: session.defaultRequestOperator?.merge(
operators: operators
))
}
}

// - MARK: PubNubConfiguration

struct PubNubConfigurationDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> PubNubConfiguration {
container.configuration
}
}

struct PubNubInstanceIDDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> UUID {
container.instanceID
}
}

// MARK: - HTTPSessions

struct DefaultHTTPSessionDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> SessionReplaceable {
HTTPSession(configuration: .pubnub)
}
}

struct HTTPSubscribeSessionDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> SessionReplaceable {
HTTPSession(
configuration: .subscription,
sessionQueue: container.httpSubscribeSessionQueue,
sessionStream: SessionListener(queue: container.httpSubscribeSessionQueue)
)
}
}

struct HTTPSubscribeSessionQueueDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> DispatchQueue {
DispatchQueue(label: "Subscribe Response Queue")
}
}

struct HTTPPresenceSessionDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> HTTPSession {
HTTPSession(
configuration: .pubnub,
sessionQueue: container.httpSubscribeSessionQueue,
sessionStream: SessionListener(queue: container.httpSubscribeSessionQueue)
)
}
}

// MARK: - FileURLSession

struct FileURLSessionDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> URLSessionReplaceable {
URLSession(
configuration: .pubnubBackground,
delegate: FileSessionManager(),
delegateQueue: .main
)
}
}

// MARK: - PresenceStateContainer

struct PresenceStateContainerDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> PubNubPresenceStateContainer {
PubNubPresenceStateContainer.shared
}
}

// MARK: SubscribeEventEngine

struct SubscribeEventEngineDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> SubscribeEngine {
let effectHandlerFactory = SubscribeEffectFactory(
session: container.httpSubscribeSession,
presenceStateContainer: container.presenceStateContainer
)
let subscribeEngine = SubscribeEngine(
state: Subscribe.UnsubscribedState(),
transition: SubscribeTransition(),
dispatcher: EffectDispatcher(factory: effectHandlerFactory),
dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: container.configuration))
)
return subscribeEngine
}
}

// MARK: PresenceEventEngine

struct PresenceEventEngineDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> PresenceEngine {
let effectHandlerFactory = PresenceEffectFactory(
session: container.httpPresenceSession,
presenceStateContainer: container.presenceStateContainer
)
let presenceEngine = PresenceEngine(
state: Presence.HeartbeatInactive(),
transition: PresenceTransition(configuration: container.configuration),
dispatcher: EffectDispatcher(factory: effectHandlerFactory),
dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: container.configuration))
)
return presenceEngine
}
}

// MARK: - SubscriptionSession

struct SubscriptionSessionDependencyKey: DependencyKey {
static func value(from container: DependencyContainer) -> SubscriptionSession {
if container.configuration.enableEventEngine {
return SubscriptionSession(
strategy: EventEngineSubscriptionSessionStrategy(
configuration: container.configuration,
subscribeEngine: container.subscribeEngine,
presenceEngine: container.presenceEngine,
presenceStateContainer: container.presenceStateContainer
)
)
} else {
return SubscriptionSession(
strategy: LegacySubscriptionSessionStrategy(
configuration: container.configuration,
network: container.httpSubscribeSession,
presenceSession: container.httpPresenceSession
)
)
}
}
}

// Provides a standard interface for objects that wrap or encapsulate other objects in a dependency container context.
protocol Wrappable<T> {
associatedtype T
var value: T? { get }
}

// A concrete implementation of the `Wrappable` protocol, designed to hold a weak reference to the object it wraps.
// It only accepts classes (reference types) as its generic parameter, because weak references
// can only be made to reference types.
private class WeakWrapper<T: AnyObject>: Wrappable {
private weak var optionalValue: T?

var value: T? {
optionalValue
}

init(_ value: T) {
self.optionalValue = value
}
}

// Holds a strong reference to the object it wraps
private class ValueWrapper<T>: Wrappable {
let value: T?

init(_ value: T) {
self.value = value
}
}
Loading
Loading