-
Notifications
You must be signed in to change notification settings - Fork 24
Adding a new Listeners API #153
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
Conversation
734f78e to
d0ec8a3
Compare
parfeon
left a comment
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 <parfeon@me.com>
Co-authored-by: Serhii Mamontov <parfeon@me.com>
|
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.
| 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.xand the underlying entity's subscription ischannel.item.*:- According to the line 195, I will compare
channel.itemwithchannel.item✅
- According to the line 195, I will compare
- The message came from
a.b.cand the underlying entity's subscription ischannel.item.*- I will compare
a.bwithchannel.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.
techwritermat
left a comment
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