SwiftStream Filtering Tutorial for Swift Native

 
The PubNub Swift 3.0 SDK contains many significant changes from the 2.x SDK, including breaking changes. Please refer to the PubNub Swift 3.0 Migration Guide for more details.

Stream filtering allows a subscriber to apply a filter to only receive messages that satisfy the conditions of the filter. The message filter is set by the subscribing client(s) but it's applied on the server side thus preventing unwanted messages (those that don't meet the conditions of the filter) from reaching the subscriber.

Stream filters are implemented with two components: meta dictionary on publish and filter expression on subscribe.

 

Filters are applied to all channels that the client is subscribed to. When messages are encrypted (using crypto key when initializing PubNub), the meta dictionary is plain text, so that the PubNub Network can properly apply the filters as required. It is important to only include information that is not confidential or otherwise requiring encryption.

To use stream filtering, it's important to include filtering information when publishing a new message. The meta field isn't included in the payload itself but allows other clients to filter on the supplied information.

To include meta, prepare the data dictionary and pass it to the publishing function as the following example.

pubnub.publish(
  channel: "channelSwift",
  message: "Hello from PubNub Swift",
  meta: [
    "my": "meta",
    "name": "PubNub"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}

With the meta information being published on publish, you can now leverage the stream filtering to omit messages that aren't important for a particular client.

 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
let config = PubNubConfiguration(
  publishKey: "YOUR-PUB-KEY",
  subscribeKey: "YOUR-SUB-KEY",
  filterExpression: "filter=expression"
)

let pubnub = PubNub(configuration: config)

Setting a filter applies to all channels that you will subscribe to from that particular client. This client filter excludes messages that have this subscriber's UUID set at the sender's UUID:

 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
let uuid = "my-uuid"
let config = PubNubConfiguration(
  publishKey: "YOUR-PUB-KEY",
  subscribeKey: "YOUR-SUB-KEY",
  uuid: uuid,
  filterExpression = "uuid != \(uuid)"
)

let pubnub = PubNub(configuration: config)

When publishing messages, you need to include the sender's UUID if you want the subscriber side client filter to work:

 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
pubnub.publish(
  channel: "channelSwift",
  message: "Hello from PubNub Swift",
  meta: [
    "uuid": uuid
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}

For the second example, publish the locale for which the published message was generated and allow the client to only receive languages in a specific language.

When publishing, the client uses the meta field to publish:

pubnub.publish(
  channel: "channelSwift",
  message: "Hello from PubNub Swift",
  meta: [
    "language": "english"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}

On the subscriber side, the filter expression is specified to make sure only english messages will come to the subscriber.

 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
let config = PubNubConfiguration(
  publishKey: "YOUR-PUB-KEY",
  subscribeKey: "YOUR-SUB-KEY",
  filterExpression = "language == 'english'"
)

let pubnub = PubNub(configuration: config)

Any messages that don't have language=english in the meta won't arrive to the client. For example, the following publish won't be received:

pubnub.publish(
  channel: "channelSwift",
  message: "Bonjour de PubNub Swift",
  meta: [
    "language": "french"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}

We can improve the second example with support for multiple languages. As the second example we're going to publish in English, French, and Spanish, but receive only French and Spanish.

pubnub.publish(
  channel: "channelSwift",
  message: "Hello from PubNub Swift",
  meta: [
    "language": "english"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
pubnub.publish(
  channel: "channelSwift",
  message: "Bonjour de PubNub Swift",
  meta: [
    "language": "french"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
pubnub.publish(
  channel: "channelSwift",
  message: "Hola de PubNub Swift",
  meta: [
    "language": "spanish"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}

On the subscribe side, we're going to use the contains operator to support multiple languages.

Be aware that the contains operator has a very low precedence. When you're using it in a more complex expression, use it with parentheses to ensure it gets evaluated in the way you expect.

 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
let config = PubNubConfiguration(
  publishKey: "YOUR-PUB-KEY",
  subscribeKey: "YOUR-SUB-KEY",
  filterExpression = "('french', 'spanish') contains language"
)

let pubnub = PubNub(configuration: config)

For the fourth example, we would like to subscribe to all languages except spanish. We begin by publishing similar to example two.

pubnub.publish(
  channel: "channelSwift",
  message: "Hello from PubNub Swift",
  meta: [
    "language": "english"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
pubnub.publish(
  channel: "channelSwift",
  message: "Bonjour de PubNub Swift",
  meta: [
    "language": "french"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
pubnub.publish(
  channel: "channelSwift",
  message: "Hola de PubNub Swift",
  meta: [
    "language": "spanish"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}

On the subscribe side, we're going to use the != operator to reject messages written in spanish.

 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
let config = PubNubConfiguration(
  publishKey: "YOUR-PUB-KEY",
  subscribeKey: "YOUR-SUB-KEY",
  filterExpression = "language != 'spanish'"
)

let pubnub = PubNub(configuration: config)
pubnub.publish(
  channel: "AAPL",
  message: "99.75",
  meta: [
    "price": "99.75",
    "channel": "AAPL"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
pubnub.publish(
  channel: "AAPL",
  message: "100.10",
  meta: [
    "price": "100.10",
    "channel": "AAPL"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
pubnub.publish(
  channel: "GOOG",
  message: "15.50",
  meta: [
    "price": "15.50",
    "channel": "GOOG"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
pubnub.publish(
  channel: "GOOG",
  message: "14.95",
  meta: [
    "price": "14.95",
    "channel": "GOOG"
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}
Client filter would be applied to all channels by default, but you could do something like this:
 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
let config = PubNubConfiguration(
  publishKey: "YOUR-PUB-KEY",
  subscribeKey: "YOUR-SUB-KEY",
  filterExpression = "(price > 100.00 && channel == 'AAPL')"
)

let pubnub = PubNub(configuration: config)

Arithmetic operations are useful when subscribing to stream readings such as temperature. In our example, we're going to publish temperature and only subscribe to events when the temperature is greater than the limit.

pubnub.publish(
  channel: "channelSwift",
  message: "Hello from PubNub Swift",
  meta: [
    "temperature": 60
  ]
) { result in
  switch result {
  case let .success(response):
    print("Successful Publish Response: \(response)")
  case let .failure(error):
    print("Failed Publish Response: \(error.localizedDescription)")
  }
}

On the subscriber side, we modify the expression to use the > operator

 Always set the UUID to uniquely identify the user or device that connects to PubNub. This UUID should be persisted, and should remain unchanged for the lifetime of the user or the device. Not setting the UUID can significantly impact your billing if your account uses the Monthly Active Users (MAUs) based pricing model, and can also lead to unexpected behavior if you have Presence enabled.
let config = PubNubConfiguration(
  publishKey: "YOUR-PUB-KEY",
  subscribeKey: "YOUR-SUB-KEY",
  filterExpression = "temperature > 60"
)

let pubnub = PubNub(configuration: config)

The filtering language is extensive, and supports advanced use cases:

compound_expression is root

<compound_expression>         ::=   <expression> | <expression> <binary_logical_op> <expression>
<binary_logical_op>           ::=   && | ||
<expression>                  ::=   (<expression>) | <operand> <comparison_operator> <operand> | <unary_logical_op> <operand>
<numeric_comparison_operator> ::=   {==, !=, <, >, <=, >=}
<string_comparison_operator>  ::=   {contains, like}
<unary_logical_op>            ::=   !
<operand>                     ::=   (<operand>) | <unary_op> <operand> | <literals> <binary_op> <literals>
<unary_op>                    ::=   ~
<binary_op>                   ::=   |, &, ^, +, -, /, \*
string == 'match'                 => exact match
string LIKE 'match*'              => LIKE operator: asterisk wildcarding, case insensitive
string LIKE 'match\*'             => LIKE operator: literal match with string containing asterisk character
('Anne','anna','Ann') like 'ann*' => LIKE operator: any of the three set members would be a sufficient match

('a','b','c') CONTAINS string     => Compare against a list of values
otherstring CONTAINS string       => Check for a substring match

(3,5,9) contains numValue         => compare number to a list of values
!((3,5,9) contains numValue)      => Negation

string contains numValue          => str(numValue) in string

numValue > (numA + numB - numC)   => compare number to an arithmetic expression
(numA ^ numB) != (numValue * 10)  => compare 2 expressions
(~numA / numB) <= numValue