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

Adding a new Listeners API #153

Merged
merged 12 commits into from
Feb 21, 2024
11 changes: 9 additions & 2 deletions .pubnub.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
---
name: swift
scm: github.com/pubnub/swift
version: "6.3.0"
version: "7.0.0"
schema: 1
changelog:
- date: 2024-02-21
version: 7.0.0
changes:
- type: feature
text: "Adding the new Listeners API."
- type: feature
text: "Enabling EventEngine by default."
- date: 2024-01-22
version: 6.3.0
changes:
Expand Down Expand Up @@ -517,7 +524,7 @@ sdks:
- distribution-type: source
distribution-repository: GitHub release
package-name: PubNub
location: https://github.com/pubnub/swift/archive/refs/tags/6.3.0.zip
location: https://github.com/pubnub/swift/archive/refs/tags/7.0.0.zip
supported-platforms:
supported-operating-systems:
macOS:
Expand Down
222 changes: 156 additions & 66 deletions PubNub.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion PubNubSwift.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'PubNubSwift'
s.version = '6.3.0'
s.version = '7.0.0'
s.homepage = 'https://github.com/pubnub/swift'
s.documentation_url = 'https://www.pubnub.com/docs/swift-native/pubnub-swift-sdk'
s.authors = { 'PubNub, Inc.' => '[email protected]' }
Expand Down
87 changes: 35 additions & 52 deletions Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,24 @@ struct SubscribeInput: Equatable {
private let channelEntries: [String: PubNubChannel]
private let groupEntries: [String: PubNubChannel]

typealias InsertingResult = (
newInput: SubscribeInput,
insertedChannels: [PubNubChannel],
insertedGroups: [PubNubChannel]
)
typealias RemovingResult = (
newInput: SubscribeInput,
removedChannels: [PubNubChannel],
removedGroups: [PubNubChannel]
)

init(channels: [PubNubChannel] = [], groups: [PubNubChannel] = []) {
self.channelEntries = channels.reduce(into: [String: PubNubChannel]()) { r, channel in _ = r.insert(channel) }
self.groupEntries = groups.reduce(into: [String: PubNubChannel]()) { r, channel in _ = r.insert(channel) }
self.channelEntries = channels.reduce(into: [String: PubNubChannel]()) { r, channel in
_ = r.insert(channel)
}
self.groupEntries = groups.reduce(into: [String: PubNubChannel]()) { r, channel in
_ = r.insert(channel)
}
}

private init(channels: [String: PubNubChannel], groups: [String: PubNubChannel]) {
Expand Down Expand Up @@ -89,52 +104,42 @@ struct SubscribeInput: Equatable {
func adding(
channels: [PubNubChannel],
and groups: [PubNubChannel]
) -> (
newInput: SubscribeInput,
insertedChannels: [PubNubChannel],
insertedGroups: [PubNubChannel]
) {
) -> SubscribeInput.InsertingResult {
// Gets a copy of current channels and channel groups
var currentChannels = channelEntries
var currentGroups = groupEntries

let insertedChannels = channels.filter { currentChannels.insert($0) }
let insertedGroups = groups.filter { currentGroups.insert($0) }

return (
return InsertingResult(
newInput: SubscribeInput(channels: currentChannels, groups: currentGroups),
insertedChannels: insertedChannels,
insertedGroups: insertedGroups
)
}

func removing(
channels: [String],
and groups: [String]
) -> (
newInput: SubscribeInput,
removedChannels: [PubNubChannel],
removedGroups: [PubNubChannel]
) {
mainChannels: [PubNubChannel],
presenceChannelsOnly: [PubNubChannel],
mainGroups: [PubNubChannel],
presenceGroupsOnly: [PubNubChannel]
) -> SubscribeInput.RemovingResult {
// Gets a copy of current channels and channel groups
var currentChannels = channelEntries
var currentGroups = groupEntries

let removedChannels = channels.compactMap {
if $0.isPresenceChannelName {
return currentChannels.unsubscribePresence($0.trimmingPresenceChannelSuffix)
} else {
return currentChannels.removeValue(forKey: $0)
}
let removedChannels = mainChannels.compactMap {
currentChannels.removeValue(forKey: $0.id)
} + presenceChannelsOnly.compactMap {
currentChannels.unsubscribePresence($0.id)
}

let removedGroups = groups.compactMap {
if $0.isPresenceChannelName {
return currentGroups.unsubscribePresence($0.trimmingPresenceChannelSuffix)
} else {
return currentGroups.removeValue(forKey: $0)
}
let removedGroups = mainGroups.compactMap {
currentGroups.removeValue(forKey: $0.id)
} + presenceGroupsOnly.compactMap {
currentGroups.unsubscribePresence($0.id)
}

return (
return RemovingResult(
newInput: SubscribeInput(channels: currentChannels, groups: currentGroups),
removedChannels: removedChannels,
removedGroups: removedGroups
Expand All @@ -158,28 +163,6 @@ extension Dictionary where Key == String, Value == PubNubChannel {
self[channel.id] = channel
return true
}

func difference(_ dict: [Key:Value]) -> [Key: Value] {
let entriesInSelfAndNotInDict = filter {
dict[$0.0] != self[$0.0]
}
return entriesInSelfAndNotInDict.reduce([Key:Value]()) { (res, entry) -> [Key:Value] in
var res = res
res[entry.0] = entry.1
return res
}
}

func intersection(_ dict: [Key:Value]) -> [Key: Value] {
let entriesInSelfAndInDict = filter {
dict[$0.0] == self[$0.0]
}
return entriesInSelfAndInDict.reduce([Key:Value]()) { (res, entry) -> [Key:Value] in
var res = res
res[entry.0] = entry.1
return res
}
}

// Updates current Dictionary with the new channel value unsubscribed from Presence.
// Returns the updated value if the corresponding entry matching the passed `id:` was found, otherwise `nil`
Expand Down
18 changes: 16 additions & 2 deletions Sources/PubNub/EventEngine/Subscribe/Subscribe.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,14 @@ extension Subscribe {
let input: SubscribeInput
let cursor: SubscribeCursor
let error: PubNubError
let connectionStatus = ConnectionStatus.connectionError
let connectionStatus: ConnectionStatus

init(input: SubscribeInput, cursor: SubscribeCursor, error: PubNubError) {
self.input = input
self.cursor = cursor
self.error = error
self.connectionStatus = .connectionError(error)
}
}

struct ReceivingState: SubscribeState {
Expand All @@ -83,7 +90,14 @@ extension Subscribe {
let input: SubscribeInput
let cursor: SubscribeCursor
let error: PubNubError
let connectionStatus = ConnectionStatus.disconnectedUnexpectedly
let connectionStatus: ConnectionStatus

init(input: SubscribeInput, cursor: SubscribeCursor, error: PubNubError) {
self.input = input
self.cursor = cursor
self.error = error
self.connectionStatus = .disconnectedUnexpectedly(error)
}
}

struct UnsubscribedState: SubscribeState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ fileprivate extension SubscribeTransition {
), invocations: [
.regular(.emitStatus(change: Subscribe.ConnectionStatusChange(
oldStatus: state.connectionStatus,
newStatus: .connectionError,
newStatus: .connectionError(error),
error: error
)))
]
Expand Down Expand Up @@ -329,7 +329,7 @@ fileprivate extension SubscribeTransition {
), invocations: [
.regular(.emitStatus(change: Subscribe.ConnectionStatusChange(
oldStatus: state.connectionStatus,
newStatus: .disconnectedUnexpectedly,
newStatus: .disconnectedUnexpectedly(error),
error: error
)))
]
Expand Down
94 changes: 94 additions & 0 deletions Sources/PubNub/Events/New/Entities/EntityCreator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// PubNub+Subscribable.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

/// Protocol for types capable of creating references for entities to which the user can subscribe,
/// receiving real-time updates.
public protocol EntityCreator {
/// Creates a new channel entity the user can subscribe to.
///
/// This method does not create any entity, either locally or remotely; it merely provides
/// a reference to a channel that can be subscribed to and unsubscribed from
///
/// - Parameters:
/// - name: The unique identifier for the channel.
/// - Returns: A `ChannelRepresentation` object representing the channel.
func channel(_ name: String) -> ChannelRepresentation

/// Creates a new channel group entity the user can subscribe to.
///
/// - Parameters:
/// - name: The unique identifier for the channel group.
/// - Returns: A `ChannelGroupRepresentation` object representing the channel group.
func channelGroup(_ name: String) -> ChannelGroupRepresentation

/// Creates user metadata entity the user can subscribe to.
///
/// This method does not create any entity, either locally or remotely; it merely provides
/// a reference to a channel that can be subscribed to and unsubscribed from
///
/// - Parameters:
/// - name: The unique identifier for the user metadata.
/// - Returns: A `UserMetadataRepresentation` object representing the user metadata.
func userMetadata(_ name: String) -> UserMetadataRepresentation

/// Creates channel metadata entity the user can subscribe to.
///
/// This method does not create any entity, either locally or remotely; it merely provides
/// a reference to a channel that can be subscribed to and unsubscribed from
///
/// - Parameters:
/// - name: The unique identifier for the channel metadata.
/// - Returns: A `ChannelMetadataRepresentation` object representing the channel metadata.
func channelMetadata(_ name: String) -> ChannelMetadataRepresentation
}

public extension EntityCreator {
/// Creates a `SubscriptionSet` object from the collection of `Subscribable` entites.
///
/// Use this function to set up and manage subscriptions for a collection of `Subscribable` entities.
///
/// - Parameters:
/// - queue: The dispatch queue on which the subscription events should be handled
/// - entities: A collection of `Subscribable` entities to subscribe to
/// - options: Additional options for configuring the subscription
/// - Returns: A `SubscriptionSet` instance for managing the specified entities.
func subscription(
queue: DispatchQueue = .main,
entities: any Collection<Subscribable>,
options: SubscriptionOptions = SubscriptionOptions.empty()
) -> SubscriptionSet {
SubscriptionSet(
queue: queue,
entities: entities,
options: options
)
}
}

// 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
)
}
47 changes: 47 additions & 0 deletions Sources/PubNub/Events/New/Entities/EntitySubscribable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// EntitySubscribable.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

// MARK: - PubNubChannelRepresentation

/// Represents a channel that can be subscribed to and unsubscribed from using the PubNub service.
public class ChannelRepresentation: Subscribable {
init(name: String, receiver: SubscribeReceiver) {
super.init(name: name, subscriptionType: .channel, receiver: receiver)
}
}

// MARK: - PubNubChannelGroupRepresentation

/// Represents a channel group that can be subscribed to and unsubscribed from using the PubNub service.
public class ChannelGroupRepresentation: Subscribable {
init(name: String, receiver: SubscribeReceiver) {
super.init(name: name, subscriptionType: .channelGroup, receiver: receiver)
}
}

// MARK: - PubNubUserMetadataRepresentation

/// Represents user metadata that can be subscribed to and unsubscribed from using the PubNub service.
public class UserMetadataRepresentation: Subscribable {
init(id: String, receiver: SubscribeReceiver) {
super.init(name: id, subscriptionType: .channel, receiver: receiver)
}
}

// MARK: - PubNubChannelMetadataRepresentation

/// Represents channel metadata that can be subscribed to and unsubscribed from using the PubNub service.
public class ChannelMetadataRepresentation: Subscribable {
init(id: String, receiver: SubscribeReceiver) {
super.init(name: id, subscriptionType: .channel, receiver: receiver)
}
}
Loading
Loading