-
Notifications
You must be signed in to change notification settings - Fork 28
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
Conversation
734f78e
to
d0ec8a3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly looks good, but there is a question on ability to have multiple listeners on Subscription
, SubscriptionSet
and PubNub
client. For example, one Subscription
instance passed in one view between components in it — there is a chance that multiple components would like to have their listener on the same event type.
channels: [PubNubChannel], | ||
presenceChannelsOnly: [PubNubChannel], | ||
groups: [PubNubChannel], | ||
presenceGroupsOnly: [PubNubChannel] | ||
) -> SubscribeInput.RemovingResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you subscribe solely on presence channels with the latest setup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I preserved existing behaviors in SDK:
- You cannot subscribe to Presence channel without main channel
- You can unsubscribe from Presence channel only and still keep main channel
The helper method you highlighted is responsible for returning the new channels and groups due to unsubscribing. I can change its signature to be more meaningful:
func removing(
mainChannels: [PubNubChannel]
presenceOnlyChannels: [PubNubChannel]
mainGroups: [PubNubChannel],
presenceOnlyGroups: [PubNubChannel]
)
/// A closure to be called when the connection status changes. | ||
var onConnectionStateChange: ((ConnectionStatus) -> Void)? { get set } | ||
/// A closure to be called when a subscription error occurs. | ||
var onSubscribeError: ((PubNubError) -> Void)? { get set } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this one here for some form of backward compatibility? If there were an error, then depending on from the current state, we would receive different connection statuses: connection error or unexpected disconnect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, that's because of the old implementation. You're right, it should be enough to read an underlying error from ConnectionStatus
enum cases like connectionError
and disconnectedUnexpectedly
and getting rid of onSubscribeError
closure you pointed out. So what I would suggest is improving existing ConnectionStatus like this:
enum ConnectionStatus {
case connectionError(PubNubError)
case disconnectedUnexpectedly(PubNubError)
}
The reason I haven't done it yes is that there's disconnectedUnexpectedly
case introduced a long time ago before EE. Now it's time to change it even if it might be a breaking change. I will run it by the team.
let pattern = "^" + self + "$" | ||
let predicate = NSPredicate(format: "SELF MATCHES %@", pattern) | ||
|
||
return predicate.evaluate(with: string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NSPredicate
is a powerful tool, but in a way how it used may affect performance compared to regular compare as on line 192.
channels: [PubNubChannel], | ||
presenceChannelsOnly: [PubNubChannel], | ||
groups: [PubNubChannel], | ||
presenceGroupsOnly: [PubNubChannel] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to subscribe on presence channels only with the current setup?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rules are still the same as I described in this comment. It's a helper method only I can always rename. In general, unsubscribing still works like that:
- If you unsubscribe from main channel, you should also unsubscribe from its Presence channel
- If you unsubscribe from Presence channel only, you should still keep main channel in the Subscribe loop
I noticed it's easier to keep these lists in separate parameters when performing next computations.
let channelSubscriptions = channels.map { | ||
channel($0).subscription( | ||
queue: queue, | ||
options: withPresence ? ReceivePresenceEvents() : SubscriptionOptions.empty() | ||
) | ||
} | ||
let channelGroupSubscriptions = groups.map { | ||
channelGroup($0).subscription( | ||
queue: queue, | ||
options: withPresence ? ReceivePresenceEvents() : SubscriptionOptions.empty() | ||
) | ||
} | ||
internalSubscribe( | ||
with: channelSubscriptions, | ||
and: channelGroupSubscriptions, | ||
at: cursor?.timetoken | ||
) | ||
channelSubscriptions.forEach { subscription in | ||
subscription.subscriptionNames.flatMap { $0 }.forEach { | ||
globalChannelSubscriptions[$0] = subscription | ||
} | ||
} | ||
channelGroupSubscriptions.forEach { subscription in | ||
subscription.subscriptionNames.flatMap { $0 }.forEach { | ||
globalGroupSubscriptions[$0] = subscription | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I understand, this one is for PubNub client instance itself?
If subscribing to multiple entities, then it will be more reasonable to create SubscriptionSet
with Subscriptions
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran it by the team. You're right that this should be kept in SubscriptionSet
under the hood. Unfortunately, Swift SDK is the only one that provides functionality like unsubscribing from Presence channels only. And this is what prevents us from using SubscriptionSet
for global subscriptions. We have no API/method for SubscriptionSet to unsubscribe from Presence channel only. I could deprecate this method or even remove withPresence
parameter, but this decision requires PM input. Can we leave it as it is and fix it in case of further decisions?
Co-authored-by: Serhii Mamontov <[email protected]>
Co-authored-by: Serhii Mamontov <[email protected]>
Regarding your comment here, I slightly modified
The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some naming questions and one about whether proper type used in method signature or not.
About multiple listeners of the same type on a single Subscription
/ SubscriptionSet
is possible only through clone()
utilization, and impossible to have multiple onMessage
listeners on the same instance.
Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeInput.swift
Outdated
Show resolved
Hide resolved
if let firstIndex = entityName.lastIndex(of: "."), let secondIndex = string.lastIndex(of: ".") { | ||
return entityName.prefix(upTo: firstIndex) == string.prefix(upTo: secondIndex) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't the "original" NSPredicate
basically matched the same what is done on the line #192
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it should. It compares two String up to the last occurrence of .
, assuming both contain the .
character. Let's suppose these scenarios:
- The message came from
channel.item.x
and the underlying entity's subscription ischannel.item.*
:- According to the line 195, I will compare
channel.item
withchannel.item
✅
- According to the line 195, I will compare
- The message came from
a.b.c
and the underlying entity's subscription ischannel.item.*
- I will compare
a.b
withchannel.item
🔴
- I will compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I finally got it.
I was totally fine with extension
for String
- it looked better.
I probably would use payload.subscription ?? payload.channel
to match both channel
and channelGroup
subscription type. If a subscription has been created for wildcard subscription, then the subscription
field will hold the same name (if an event for one of the channels from it) and will be no need for pattern match (just plain ==
). The only thing which may require some attention is presence channels / channel group names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
enable event engine please :)
* Fixes in unit tests after enabling EE by default
@pubnub-release-bot release as 7.0.0 |
🚀 Release successfully completed 🚀 |
feat(listeners): adding the new Listeners API
feat(subscribe & presence): enabling EventEngine by default