Skip to content

Commit

Permalink
Upgrading PubNub Swift SDK:
Browse files Browse the repository at this point in the history
* Adding possibility to attach listeners to Subscription and SubscriptionSet
* Ensuring that listeners are always internally stored as weak references
* Prevent from dispatching .unubscribeAll if the current state is UnsubscribedState
* Removing .connecting and .reconnecting ConnectionStatus
* Introducing .subscriptionChanged ConnectionStatus
  • Loading branch information
jguz-pubnub committed Aug 20, 2024
1 parent 4952f41 commit 13a6d98
Show file tree
Hide file tree
Showing 28 changed files with 875 additions and 581 deletions.
6 changes: 2 additions & 4 deletions Examples/Sources/DetailTableViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,12 +249,10 @@ class DetailTableViewController: UITableViewController {
print("The signal is \(signal.payload) and was sent by \(signal.publisher ?? "")")
case let .connectionStatusChanged(connectionChange):
switch connectionChange {
case .connecting:
print("Status connecting...")
case .connected:
print("Status connected!")
case .reconnecting:
print("Status reconnecting...")
case .subscriptionChanged:
print("Subscription changed")
case .disconnected:
print("Status disconnected")
case .disconnectedUnexpectedly:
Expand Down
12 changes: 8 additions & 4 deletions PubNub.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@
3D758DD62AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DD42AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift */; };
3D8BAC102B8C96D70059A5C3 /* DependencyContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8BAC0F2B8C96D70059A5C3 /* DependencyContainer.swift */; };
3D9134972A1216F7000A5124 /* PubNubPushTargetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9134962A1216F7000A5124 /* PubNubPushTargetTests.swift */; };
3DA0C7D02BFE59AC000FFE6C /* SubscriptionListenersContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DA0C7CF2BFE59AC000FFE6C /* SubscriptionListenersContainer.swift */; };
3DACC7F72AB88F8E00210B14 /* Data+CommonCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */; };
3DB9255C2B7A2B89001B7E90 /* SubscriptionStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925592B7A2B89001B7E90 /* SubscriptionStreamTests.swift */; };
3DB9255D2B7A2B89001B7E90 /* EventStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB9255A2B7A2B89001B7E90 /* EventStreamTests.swift */; };
Expand All @@ -441,7 +442,7 @@
3DB925812B7AA75F001B7E90 /* SubscribeMessagePayload+PubNubEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925732B7AA75F001B7E90 /* SubscribeMessagePayload+PubNubEvent.swift */; };
3DB925822B7AA75F001B7E90 /* Subscribable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925742B7AA75F001B7E90 /* Subscribable.swift */; };
3DB925832B7AA75F001B7E90 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925752B7AA75F001B7E90 /* Subscription.swift */; };
3DB925842B7AA75F001B7E90 /* EventEmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925762B7AA75F001B7E90 /* EventEmitter.swift */; };
3DB925842B7AA75F001B7E90 /* EventListenerInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925762B7AA75F001B7E90 /* EventListenerInterface.swift */; };
3DB925852B7AA75F001B7E90 /* EntityCreator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925782B7AA75F001B7E90 /* EntityCreator.swift */; };
3DB925862B7AA75F001B7E90 /* EntitySubscribable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925792B7AA75F001B7E90 /* EntitySubscribable.swift */; };
3DB925872B7AA75F001B7E90 /* SessionStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB9257B2B7AA75F001B7E90 /* SessionStream.swift */; };
Expand Down Expand Up @@ -1016,6 +1017,7 @@
3D758DD42AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptorHeaderWithinStreamFinder.swift; sourceTree = "<group>"; };
3D8BAC0F2B8C96D70059A5C3 /* DependencyContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyContainer.swift; sourceTree = "<group>"; };
3D9134962A1216F7000A5124 /* PubNubPushTargetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubPushTargetTests.swift; sourceTree = "<group>"; };
3DA0C7CF2BFE59AC000FFE6C /* SubscriptionListenersContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionListenersContainer.swift; sourceTree = "<group>"; };
3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+CommonCrypto.swift"; sourceTree = "<group>"; };
3DB925592B7A2B89001B7E90 /* SubscriptionStreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionStreamTests.swift; sourceTree = "<group>"; };
3DB9255A2B7A2B89001B7E90 /* EventStreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventStreamTests.swift; sourceTree = "<group>"; };
Expand All @@ -1031,7 +1033,7 @@
3DB925732B7AA75F001B7E90 /* SubscribeMessagePayload+PubNubEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SubscribeMessagePayload+PubNubEvent.swift"; sourceTree = "<group>"; };
3DB925742B7AA75F001B7E90 /* Subscribable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Subscribable.swift; sourceTree = "<group>"; };
3DB925752B7AA75F001B7E90 /* Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = "<group>"; };
3DB925762B7AA75F001B7E90 /* EventEmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventEmitter.swift; sourceTree = "<group>"; };
3DB925762B7AA75F001B7E90 /* EventListenerInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventListenerInterface.swift; sourceTree = "<group>"; };
3DB925782B7AA75F001B7E90 /* EntityCreator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityCreator.swift; sourceTree = "<group>"; };
3DB925792B7AA75F001B7E90 /* EntitySubscribable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntitySubscribable.swift; sourceTree = "<group>"; };
3DB9257B2B7AA75F001B7E90 /* SessionStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SessionStream.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2254,10 +2256,11 @@
isa = PBXGroup;
children = (
3DB925712B7AA75F001B7E90 /* PubNubEvent.swift */,
3DB925762B7AA75F001B7E90 /* EventEmitter.swift */,
3DB925762B7AA75F001B7E90 /* EventListenerInterface.swift */,
3DB925742B7AA75F001B7E90 /* Subscribable.swift */,
3DB925752B7AA75F001B7E90 /* Subscription.swift */,
3DB925702B7AA75F001B7E90 /* SubscriptionSet.swift */,
3DA0C7CF2BFE59AC000FFE6C /* SubscriptionListenersContainer.swift */,
3DB925772B7AA75F001B7E90 /* Entities */,
3DB925722B7AA75F001B7E90 /* Extensions */,
);
Expand Down Expand Up @@ -3442,6 +3445,7 @@
3D6265D72ABCA79100FDD5E6 /* CryptorUtils.swift in Sources */,
35D8D4C522EB4600001B07D9 /* AnyJSON.swift in Sources */,
35AC16332487179400A66030 /* PubNubPage.swift in Sources */,
3DA0C7D02BFE59AC000FFE6C /* SubscriptionListenersContainer.swift in Sources */,
3DB925802B7AA75F001B7E90 /* PubNubEvent.swift in Sources */,
35AC162F2486C9A400A66030 /* PubNubMessageAction.swift in Sources */,
3585033B22CD545400A11D9A /* URLRequest+PubNub.swift in Sources */,
Expand Down Expand Up @@ -3519,7 +3523,7 @@
3D389FE92B35AF4A006928E7 /* SubscribeEffectFactory.swift in Sources */,
355F213722DECFCD004DEFBF /* Typealias+PubNub.swift in Sources */,
3585A02623C63F3900FDA860 /* DecodingError+PubNub.swift in Sources */,
3DB925842B7AA75F001B7E90 /* EventEmitter.swift in Sources */,
3DB925842B7AA75F001B7E90 /* EventListenerInterface.swift in Sources */,
3557CE0723886434004BBACC /* PubNubAPNSPayload.swift in Sources */,
3D389FED2B35AF4A006928E7 /* Subscribe.swift in Sources */,
3D389FE22B35AF4A006928E7 /* Dispatcher.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ class MessageCache {
struct EmitMessagesEffect: EffectHandler {
let messages: [SubscribeMessagePayload]
let cursor: SubscribeCursor
let listeners: [BaseSubscriptionListener]
let listeners: WeakSet<BaseSubscriptionListener>
let messageCache: MessageCache

func performTask(completionBlock: @escaping ([Subscribe.Event]) -> Void) {
// Attempt to detect missed messages due to queue overflow
if messages.count >= 100 {
listeners.forEach {
$0.emit(subscribe: .errorReceived(
$0?.emit(subscribe: .errorReceived(
PubNubError(
.messageCountExceededMaximum,
router: nil,
Expand All @@ -68,7 +68,7 @@ struct EmitMessagesEffect: EffectHandler {
}

listeners.forEach {
$0.emit(batch: filteredMessages)
$0?.emit(batch: filteredMessages)
}

completionBlock([])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,16 @@ import Foundation

struct EmitStatusEffect: EffectHandler {
let statusChange: Subscribe.ConnectionStatusChange
let listeners: [BaseSubscriptionListener]
let listeners: WeakSet<BaseSubscriptionListener>

func performTask(completionBlock: @escaping ([Subscribe.Event]) -> Void) {
if let error = statusChange.error {
listeners.forEach {
$0.emit(subscribe: .errorReceived(error))
$0?.emit(subscribe: .errorReceived(error))
}
}
listeners.forEach {
$0.emit(subscribe: .connectionChanged(statusChange.newStatus))
$0?.emit(subscribe: .connectionChanged(statusChange.newStatus))
}
completionBlock([])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import Foundation
class HandshakeEffect: EffectHandler {
private let subscribeEffect: SubscribeEffect

init(request: SubscribeRequest, listeners: [BaseSubscriptionListener]) {
init(request: SubscribeRequest, listeners: WeakSet<BaseSubscriptionListener>) {
self.subscribeEffect = SubscribeEffect(
request: request,
listeners: listeners,
Expand All @@ -25,7 +25,6 @@ class HandshakeEffect: EffectHandler {
}

func performTask(completionBlock: @escaping ([Subscribe.Event]) -> Void) {
subscribeEffect.listeners.forEach { $0.emit(subscribe: .connectionChanged(.connecting)) }
subscribeEffect.performTask(completionBlock: completionBlock)
}

Expand All @@ -43,7 +42,7 @@ class HandshakeEffect: EffectHandler {
class ReceivingEffect: EffectHandler {
private let subscribeEffect: SubscribeEffect

init(request: SubscribeRequest, listeners: [BaseSubscriptionListener]) {
init(request: SubscribeRequest, listeners: WeakSet<BaseSubscriptionListener>) {
self.subscribeEffect = SubscribeEffect(
request: request,
listeners: listeners,
Expand All @@ -69,13 +68,13 @@ class ReceivingEffect: EffectHandler {

private class SubscribeEffect: EffectHandler {
let request: SubscribeRequest
let listeners: [BaseSubscriptionListener]
let listeners: WeakSet<BaseSubscriptionListener>
let onResponseReceived: (SubscribeResponse) -> Subscribe.Event
let onErrorReceived: (PubNubError) -> Subscribe.Event

init(
request: SubscribeRequest,
listeners: [BaseSubscriptionListener],
listeners: WeakSet<BaseSubscriptionListener>,
onResponseReceived: @escaping ((SubscribeResponse) -> Subscribe.Event),
onErrorReceived: @escaping ((PubNubError) -> Subscribe.Event)
) {
Expand All @@ -91,7 +90,7 @@ private class SubscribeEffect: EffectHandler {
switch $0 {
case .success(let response):
selfRef.listeners.forEach {
$0.emit(subscribe: .responseReceived(
$0?.emit(subscribe: .responseReceived(
SubscribeResponseHeader(
channels: selfRef.request.channels.map { PubNubChannel(channel: $0) },
groups: selfRef.request.groups.map { PubNubChannel(channel: $0) },
Expand Down
11 changes: 7 additions & 4 deletions Sources/PubNub/EventEngine/Subscribe/Subscribe.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension Subscribe {
struct HandshakingState: SubscribeState {
let input: SubscribeInput
let cursor: SubscribeCursor
let connectionStatus = ConnectionStatus.connecting
let connectionStatus = ConnectionStatus.disconnected
}

struct HandshakeStoppedState: SubscribeState {
Expand All @@ -61,7 +61,7 @@ extension Subscribe {
struct ReceivingState: SubscribeState {
let input: SubscribeInput
let cursor: SubscribeCursor
let connectionStatus = ConnectionStatus.connected
let connectionStatus: ConnectionStatus
}

struct ReceiveStoppedState: SubscribeState {
Expand Down Expand Up @@ -119,9 +119,12 @@ extension Subscribe {
extension Subscribe {
struct Dependencies {
let configuration: PubNubConfiguration
let listeners: [BaseSubscriptionListener]
let listeners: WeakSet<BaseSubscriptionListener>

init(configuration: PubNubConfiguration, listeners: [BaseSubscriptionListener] = []) {
init(
configuration: PubNubConfiguration,
listeners: WeakSet<BaseSubscriptionListener> = WeakSet<BaseSubscriptionListener>([])
) {
self.configuration = configuration
self.listeners = listeners
}
Expand Down
110 changes: 76 additions & 34 deletions Sources/PubNub/EventEngine/Subscribe/SubscribeTransition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class SubscribeTransition: TransitionProtocol {
case .subscriptionRestored:
return true
case .unsubscribeAll:
return true
return !(state is Subscribe.UnsubscribedState)
case .disconnect:
return !(
state is Subscribe.HandshakeStoppedState || state is Subscribe.ReceiveStoppedState ||
Expand Down Expand Up @@ -140,25 +140,64 @@ fileprivate extension SubscribeTransition {

if newInput.isEmpty {
return setUnsubscribedState(from: state)
} else {
switch state {
case is Subscribe.HandshakingState:
return TransitionResult(state: Subscribe.HandshakingState(input: newInput, cursor: cursor))
case is Subscribe.HandshakeStoppedState:
return TransitionResult(state: Subscribe.HandshakeStoppedState(input: newInput, cursor: cursor))
case is Subscribe.HandshakeFailedState:
return TransitionResult(state: Subscribe.HandshakingState(input: newInput, cursor: cursor))
case is Subscribe.ReceivingState:
return TransitionResult(state: Subscribe.ReceivingState(input: newInput, cursor: cursor))
case is Subscribe.ReceiveStoppedState:
return TransitionResult(state: Subscribe.ReceiveStoppedState(input: newInput, cursor: cursor))
case is Subscribe.ReceiveFailedState:
return TransitionResult(state: Subscribe.HandshakingState(input: newInput, cursor: cursor))
case is Subscribe.UnsubscribedState:
return TransitionResult(state: Subscribe.HandshakingState(input: newInput, cursor: cursor))
default:
return TransitionResult(state: state)
}
}

let invocations: [EffectInvocation<Invocation>] = state is Subscribe.ReceivingState ? [
.regular(.emitStatus(change: Subscribe.ConnectionStatusChange(
oldStatus: state.connectionStatus,
newStatus: .subscriptionChanged(
channels: newInput.subscribedChannelNames,
groups: newInput.subscribedGroupNames
),
error: nil
)))
] : []

switch state {
case is Subscribe.HandshakingState:
return TransitionResult(
state: Subscribe.HandshakingState(input: newInput, cursor: cursor),
invocations: invocations
)
case is Subscribe.HandshakeStoppedState:
return TransitionResult(
state: Subscribe.HandshakeStoppedState(input: newInput, cursor: cursor),
invocations: invocations
)
case is Subscribe.HandshakeFailedState:
return TransitionResult(
state: Subscribe.HandshakingState(input: newInput, cursor: cursor),
invocations: invocations
)
case is Subscribe.ReceivingState:
let newStatus: ConnectionStatus = .subscriptionChanged(
channels: newInput.subscribedChannelNames,
groups: newInput.subscribedGroupNames
)
return TransitionResult(
state: Subscribe.ReceivingState(input: newInput, cursor: cursor, connectionStatus: newStatus),
invocations: invocations
)
case is Subscribe.ReceiveStoppedState:
return TransitionResult(
state: Subscribe.ReceiveStoppedState(input: newInput, cursor: cursor),
invocations: invocations
)
case is Subscribe.ReceiveFailedState:
return TransitionResult(
state: Subscribe.HandshakingState(input: newInput, cursor: cursor),
invocations: invocations
)
case is Subscribe.UnsubscribedState:
return TransitionResult(
state: Subscribe.HandshakingState(input: newInput, cursor: cursor),
invocations: invocations
)
default:
return TransitionResult(
state: state,
invocations: invocations
)
}
}
}
Expand Down Expand Up @@ -202,27 +241,30 @@ fileprivate extension SubscribeTransition {
messages: [SubscribeMessagePayload] = []
) -> TransitionResult<State, Invocation> {
let emitMessagesInvocation = EffectInvocation.managed(
Subscribe.Invocation.emitMessages(events: messages, forCursor: cursor)
)
let emitStatusInvocation = EffectInvocation.regular(
Subscribe.Invocation.emitStatus(change: Subscribe.ConnectionStatusChange(
oldStatus: state.connectionStatus,
newStatus: .connected,
error: nil
))
Subscribe.Invocation.emitMessages(
events: messages,
forCursor: cursor
)
)

if state is Subscribe.HandshakingState {
return TransitionResult(
state: Subscribe.ReceivingState(input: state.input, cursor: cursor),
invocations: [messages.isEmpty ? nil : emitMessagesInvocation, emitStatusInvocation].compactMap { $0 }
let emitStatusInvocation = EffectInvocation.regular(
Subscribe.Invocation.emitStatus(change: Subscribe.ConnectionStatusChange(
oldStatus: state.connectionStatus,
newStatus: .connected,
error: nil
))
)
} else {
return TransitionResult(
state: Subscribe.ReceivingState(input: state.input, cursor: cursor),
invocations: [messages.isEmpty ? nil : emitMessagesInvocation].compactMap { $0 }
state: Subscribe.ReceivingState(input: state.input, cursor: cursor, connectionStatus: .connected),
invocations: [messages.isEmpty ? nil : emitMessagesInvocation, emitStatusInvocation].compactMap { $0 }
)
}

return TransitionResult(
state: Subscribe.ReceivingState(input: state.input, cursor: cursor, connectionStatus: state.connectionStatus),
invocations: [messages.isEmpty ? nil : emitMessagesInvocation].compactMap { $0 }
)
}
}

Expand Down
21 changes: 1 addition & 20 deletions Sources/PubNub/Events/New/Entities/EntityCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Foundation

/// Protocol for types capable of creating references for entities to which the user can subscribe,
/// receiving real-time updates.
public protocol EntityCreator {
public protocol EntityCreator: AnyObject {
/// Creates a new channel entity the user can subscribe to.
///
/// This method does not create any entity, either locally or remotely; it merely provides
Expand Down Expand Up @@ -73,22 +73,3 @@ public extension EntityCreator {
)
}
}

// This internal protocol is designed for types capable of receiving an intent
// to Subscribe or Unsubscribe and invoking the PubNub service with computed channels
// and channel groups.
protocol SubscribeReceiver: AnyObject {
func registerAdapter(_ adapter: BaseSubscriptionListenerAdapter)
func hasRegisteredAdapter(with uuid: UUID) -> Bool

func internalSubscribe(
with channels: [Subscription],
and groups: [Subscription],
at timetoken: Timetoken?
)
func internalUnsubscribe(
from channels: [Subscription],
and groups: [Subscription],
presenceOnly: Bool
)
}
Loading

0 comments on commit 13a6d98

Please sign in to comment.