Skip to content

Commit 1c6a045

Browse files
authored
Add the ability to subscribe to presence channels only, without their main counterparts (#217)
1 parent 1615d3c commit 1c6a045

23 files changed

+657
-793
lines changed

.pubnub.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
---
22
name: swift
33
scm: github.com/pubnub/swift
4-
version: "9.2.3"
4+
version: "9.3.0"
55
schema: 1
66
changelog:
7+
- date: 2025-07-29
8+
version: 9.3.0
9+
changes:
10+
- type: feature
11+
text: "Add the ability to subscribe to presence channels only, without their main counterparts."
712
- date: 2025-07-29
813
version: 9.2.3
914
changes:
@@ -703,7 +708,7 @@ sdks:
703708
- distribution-type: source
704709
distribution-repository: GitHub release
705710
package-name: PubNub
706-
location: https://github.com/pubnub/swift/archive/refs/tags/9.2.3.zip
711+
location: https://github.com/pubnub/swift/archive/refs/tags/9.3.0.zip
707712
supported-platforms:
708713
supported-operating-systems:
709714
macOS:

PubNub.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4031,7 +4031,7 @@
40314031
"@loader_path/Frameworks",
40324032
);
40334033
MACOSX_DEPLOYMENT_TARGET = 10.15;
4034-
MARKETING_VERSION = 9.2.3;
4034+
MARKETING_VERSION = 9.3.0;
40354035
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
40364036
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
40374037
MTL_FAST_MATH = YES;
@@ -4082,7 +4082,7 @@
40824082
"@loader_path/Frameworks",
40834083
);
40844084
MACOSX_DEPLOYMENT_TARGET = 10.15;
4085-
MARKETING_VERSION = 9.2.3;
4085+
MARKETING_VERSION = 9.3.0;
40864086
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
40874087
MTL_ENABLE_DEBUG_INFO = NO;
40884088
MTL_FAST_MATH = YES;
@@ -4190,7 +4190,7 @@
41904190
"@loader_path/Frameworks",
41914191
);
41924192
MACOSX_DEPLOYMENT_TARGET = 10.15;
4193-
MARKETING_VERSION = 9.2.3;
4193+
MARKETING_VERSION = 9.3.0;
41944194
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
41954195
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
41964196
MTL_FAST_MATH = YES;
@@ -4243,7 +4243,7 @@
42434243
"@loader_path/Frameworks",
42444244
);
42454245
MACOSX_DEPLOYMENT_TARGET = 10.15;
4246-
MARKETING_VERSION = 9.2.3;
4246+
MARKETING_VERSION = 9.3.0;
42474247
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
42484248
MTL_ENABLE_DEBUG_INFO = NO;
42494249
MTL_FAST_MATH = YES;
@@ -4364,7 +4364,7 @@
43644364
"@loader_path/Frameworks",
43654365
);
43664366
MACOSX_DEPLOYMENT_TARGET = 10.15;
4367-
MARKETING_VERSION = 9.2.3;
4367+
MARKETING_VERSION = 9.3.0;
43684368
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
43694369
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
43704370
MTL_FAST_MATH = YES;
@@ -4416,7 +4416,7 @@
44164416
"@loader_path/Frameworks",
44174417
);
44184418
MACOSX_DEPLOYMENT_TARGET = 10.15;
4419-
MARKETING_VERSION = 9.2.3;
4419+
MARKETING_VERSION = 9.3.0;
44204420
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17";
44214421
MTL_ENABLE_DEBUG_INFO = NO;
44224422
MTL_FAST_MATH = YES;
@@ -4896,7 +4896,7 @@
48964896
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
48974897
);
48984898
MACOSX_DEPLOYMENT_TARGET = 10.15;
4899-
MARKETING_VERSION = 9.2.3;
4899+
MARKETING_VERSION = 9.3.0;
49004900
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14";
49014901
OTHER_CFLAGS = "$(inherited)";
49024902
OTHER_LDFLAGS = "$(inherited)";
@@ -4939,7 +4939,7 @@
49394939
"$(TOOLCHAIN_DIR)/usr/lib/swift/macosx",
49404940
);
49414941
MACOSX_DEPLOYMENT_TARGET = 10.15;
4942-
MARKETING_VERSION = 9.2.3;
4942+
MARKETING_VERSION = 9.3.0;
49434943
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++14";
49444944
OTHER_CFLAGS = "$(inherited)";
49454945
OTHER_LDFLAGS = "$(inherited)";

PubNubSwift.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'PubNubSwift'
3-
s.version = '9.2.3'
3+
s.version = '9.3.0'
44
s.homepage = 'https://github.com/pubnub/swift'
55
s.documentation_url = 'https://www.pubnub.com/docs/swift-native/pubnub-swift-sdk'
66
s.authors = { 'PubNub, Inc.' => '[email protected]' }

Sources/PubNub/EventEngine/Presence/PresenceTransition.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@ class PresenceTransition: TransitionProtocol {
2323

2424
func canTransition(from state: State, dueTo event: Event) -> Bool {
2525
switch event {
26-
case .joined:
27-
return true
28-
case .left:
29-
return true
26+
case let .joined(channels, channelGroups):
27+
return channels.count > 0 || channelGroups.count > 0
28+
case let .left(channels, channelGroups):
29+
return channels.count > 0 || channelGroups.count > 0
3030
case .heartbeatSuccess:
3131
return state is Presence.Heartbeating
3232
case .heartbeatFailed:

Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeInput.swift

Lines changed: 64 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -10,171 +10,95 @@
1010

1111
import Foundation
1212

13+
/// A container for the current subscribed channels and channel groups
1314
struct SubscribeInput: Equatable {
14-
private let channelEntries: [String: PubNubChannel]
15-
private let groupEntries: [String: PubNubChannel]
16-
17-
// swiftlint:disable:next large_tuple
18-
typealias InsertingResult = (
19-
newInput: SubscribeInput,
20-
insertedChannels: [PubNubChannel],
21-
insertedGroups: [PubNubChannel]
22-
)
23-
// swiftlint:disable:next large_tuple
24-
typealias RemovingResult = (
25-
newInput: SubscribeInput,
26-
removedChannels: [PubNubChannel],
27-
removedGroups: [PubNubChannel]
28-
)
29-
30-
init(channels: [PubNubChannel] = [], groups: [PubNubChannel] = []) {
31-
self.channelEntries = channels.reduce(into: [String: PubNubChannel]()) { r, channel in
32-
_ = r.insert(channel)
33-
}
34-
self.groupEntries = groups.reduce(into: [String: PubNubChannel]()) { r, channel in
35-
_ = r.insert(channel)
36-
}
37-
}
38-
39-
private init(channels: [String: PubNubChannel], groups: [String: PubNubChannel]) {
40-
self.channelEntries = channels
41-
self.groupEntries = groups
42-
}
43-
44-
var isEmpty: Bool {
45-
channelEntries.isEmpty && groupEntries.isEmpty
46-
}
47-
48-
var channels: [PubNubChannel] {
49-
Array(channelEntries.values)
50-
}
51-
52-
var groups: [PubNubChannel] {
53-
Array(groupEntries.values)
54-
}
15+
private let subscribedChannels: Set<String>
16+
private let subscribedChannelGroups: Set<String>
5517

56-
var subscribedChannelNames: [String] {
57-
channelEntries.map { $0.key }
18+
/// Result of comparing two SubscribeInput instances
19+
struct Difference {
20+
/// Items that were added (present in new but not in old)
21+
let addedChannels: Set<String>
22+
/// Channels that were removed (present in old but not in new)
23+
let removedChannels: Set<String>
24+
/// Channel groups that were added (present in new but not in old)
25+
let addedChannelGroups: Set<String>
26+
/// Channel groups that were removed (present in old but not in new)
27+
let removedChannelGroups: Set<String>
5828
}
5929

60-
var subscribedGroupNames: [String] {
61-
groupEntries.map { $0.key }
30+
init(channels: Set<String> = [], channelGroups: Set<String> = []) {
31+
self.subscribedChannels = channels
32+
self.subscribedChannelGroups = channelGroups
6233
}
6334

64-
var allSubscribedChannelNames: [String] {
65-
channelEntries.reduce(into: [String]()) { result, entry in
66-
result.append(entry.value.id)
67-
if entry.value.isPresenceSubscribed {
68-
result.append(entry.value.presenceId)
69-
}
70-
}
35+
init(channels: [String] = [], channelGroups: [String] = []) {
36+
self.subscribedChannels = Set(channels)
37+
self.subscribedChannelGroups = Set(channelGroups)
7138
}
7239

73-
var allSubscribedGroupNames: [String] {
74-
groupEntries.reduce(into: [String]()) { result, entry in
75-
result.append(entry.value.id)
76-
if entry.value.isPresenceSubscribed {
77-
result.append(entry.value.presenceId)
78-
}
79-
}
40+
/// Whether the subscribe input is empty
41+
///
42+
/// This is true if there are no subscribed channels or channel groups
43+
var isEmpty: Bool {
44+
subscribedChannels.isEmpty && subscribedChannelGroups.isEmpty
8045
}
8146

82-
var presenceSubscribedChannelNames: [String] {
83-
channelEntries.compactMap {
84-
if $0.value.isPresenceSubscribed {
85-
return $0.value.id
86-
} else {
87-
return nil
88-
}
89-
}
47+
/// Returns the names of all subscribed channels according to the `withPresence` parameter
48+
///
49+
/// If `withPresence` is true, this list includes both regular and presence channel names
50+
/// If `withPresence` is false, this list does not include presence channel names
51+
func channelNames(withPresence: Bool) -> [String] {
52+
withPresence ? subscribedChannels.allObjects : subscribedChannels.allObjects.filter { !$0.isPresenceChannelName }
9053
}
9154

92-
var presenceSubscribedGroupNames: [String] {
93-
groupEntries.compactMap {
94-
if $0.value.isPresenceSubscribed {
95-
return $0.value.id
96-
} else {
97-
return nil
98-
}
99-
}
55+
/// Returns the names of all subscribed channel groups according to the `withPresence` parameter
56+
///
57+
/// If `withPresence` is true, this list includes both regular and presence channel group names
58+
/// If `withPresence` is false, this list does not include presence channel group names
59+
func channelGroupNames(withPresence: Bool) -> [String] {
60+
withPresence ? subscribedChannelGroups.allObjects : subscribedChannelGroups.allObjects.filter { !$0.isPresenceChannelName }
10061
}
10162

63+
/// Total number of subscribed channels and channel groups
10264
var totalSubscribedCount: Int {
103-
channelEntries.count + groupEntries.count
65+
subscribedChannels.count + subscribedChannelGroups.count
10466
}
10567

106-
func adding(
107-
channels: [PubNubChannel],
108-
and groups: [PubNubChannel]
109-
) -> SubscribeInput.InsertingResult {
110-
// Gets a copy of current channels and channel groups
111-
var currentChannels = channelEntries
112-
var currentGroups = groupEntries
113-
114-
let insertedChannels = channels.filter { currentChannels.insert($0) }
115-
let insertedGroups = groups.filter { currentGroups.insert($0) }
116-
117-
return InsertingResult(
118-
newInput: SubscribeInput(channels: currentChannels, groups: currentGroups),
119-
insertedChannels: insertedChannels,
120-
insertedGroups: insertedGroups
68+
/// Adds the given channels and channel groups and returns a new input without modifying the current one
69+
func adding(channels: Set<String>, and channelGroups: Set<String>) -> SubscribeInput {
70+
SubscribeInput(
71+
channels: channels.union(subscribedChannels),
72+
channelGroups: channelGroups.union(subscribedChannelGroups)
12173
)
12274
}
12375

124-
func removing(
125-
mainChannels: [PubNubChannel],
126-
presenceChannelsOnly: [PubNubChannel],
127-
mainGroups: [PubNubChannel],
128-
presenceGroupsOnly: [PubNubChannel]
129-
) -> SubscribeInput.RemovingResult {
130-
// Gets a copy of current channels and channel groups
131-
var currentChannels = channelEntries
132-
var currentGroups = groupEntries
133-
134-
let removedChannels = mainChannels.compactMap {
135-
currentChannels.removeValue(forKey: $0.id)
136-
} + presenceChannelsOnly.compactMap {
137-
currentChannels.unsubscribePresence($0.id)
138-
}
139-
let removedGroups = mainGroups.compactMap {
140-
currentGroups.removeValue(forKey: $0.id)
141-
} + presenceGroupsOnly.compactMap {
142-
currentGroups.unsubscribePresence($0.id)
143-
}
144-
return RemovingResult(
145-
newInput: SubscribeInput(channels: currentChannels, groups: currentGroups),
146-
removedChannels: removedChannels,
147-
removedGroups: removedGroups
76+
/// Removes the given channels and channel groups and returns a new input without modifying the current one
77+
func removing(channels: Set<String>, and channelGroups: Set<String>) -> SubscribeInput {
78+
SubscribeInput(
79+
channels: subscribedChannels.subtracting(channels),
80+
channelGroups: subscribedChannelGroups.subtracting(channelGroups)
14881
)
14982
}
15083

151-
static func == (lhs: SubscribeInput, rhs: SubscribeInput) -> Bool {
152-
let equalChannels = lhs.allSubscribedChannelNames.sorted(by: <) == rhs.allSubscribedChannelNames.sorted(by: <)
153-
let equalGroups = lhs.allSubscribedGroupNames.sorted(by: <) == rhs.allSubscribedGroupNames.sorted(by: <)
84+
/// Compares this input with another and returns the differences
85+
func difference(from other: SubscribeInput) -> Difference {
86+
let addedChannels = subscribedChannels.subtracting(other.subscribedChannels)
87+
let removedChannels = other.subscribedChannels.subtracting(subscribedChannels)
15488

155-
return equalChannels && equalGroups
156-
}
157-
}
89+
let addedGroups = subscribedChannelGroups.subtracting(other.subscribedChannelGroups)
90+
let removedGroups = other.subscribedChannelGroups.subtracting(subscribedChannelGroups)
15891

159-
extension Dictionary where Key == String, Value == PubNubChannel {
160-
// Inserts and returns the provided channel if that channel doesn't already exist
161-
mutating func insert(_ channel: Value) -> Bool {
162-
if let match = self[channel.id], match == channel {
163-
return false
164-
}
165-
self[channel.id] = channel
166-
return true
92+
return Difference(
93+
addedChannels: addedChannels,
94+
removedChannels: removedChannels,
95+
addedChannelGroups: addedGroups,
96+
removedChannelGroups: removedGroups
97+
)
16798
}
16899

169-
// Updates current Dictionary with the new channel value unsubscribed from Presence.
170-
// Returns the updated value if the corresponding entry matching the passed `id:` was found, otherwise `nil`
171-
@discardableResult mutating func unsubscribePresence(_ id: String) -> Value? {
172-
if let match = self[id], match.isPresenceSubscribed {
173-
let updatedChannel = PubNubChannel(id: match.id, withPresence: false)
174-
self[match.id] = updatedChannel
175-
return updatedChannel
176-
}
177-
return nil
100+
static func == (lhs: SubscribeInput, rhs: SubscribeInput) -> Bool {
101+
lhs.subscribedChannels == rhs.subscribedChannels && lhs.subscribedChannelGroups == rhs.subscribedChannelGroups
178102
}
179103
}
180104

@@ -183,8 +107,8 @@ extension SubscribeInput: CustomStringConvertible {
183107
String.formattedDescription(
184108
self,
185109
arguments: [
186-
("channels", allSubscribedChannelNames),
187-
("groups", allSubscribedGroupNames)
110+
("channels", channelNames(withPresence: true)),
111+
("groups", channelGroupNames(withPresence: true))
188112
]
189113
)
190114
}

Sources/PubNub/EventEngine/Subscribe/Subscribe.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ extension Subscribe.ReceiveFailedState {
129129
extension Subscribe {
130130
struct UnsubscribedState: SubscribeState {
131131
let cursor: SubscribeCursor = .init(timetoken: 0, region: 0)
132-
let input: SubscribeInput = .init()
132+
let input: SubscribeInput = .init(channels: [], channelGroups: [])
133133
let connectionStatus = ConnectionStatus.disconnected
134134
}
135135
}

0 commit comments

Comments
 (0)