iOSiOSCocoaObjective-CiOS SDK V4 Publish/Subscribe Tutorial for Realtime Apps

PubNub utilizes a Publish/Subscribe model for real-time data streaming and device signaling which lets you establish and maintain persistent socket connections to any device and push data to global audiences in less than ¼ of a second.
The atomic components that make up a data stream are API Keys, Messages, and Channels.
To build an application that leverages the PubNub Network for Data Streams with Publish and Subscribe, you will need PubNub API Keys which we provide when you Sign-Up.

You will need at the minimum a subscribeKey and publishKey. If a client will only subscribe, and not publish, then the client only need to initialize with the subscribeKey. For clients who will be publishing only, or publishing and subscribing (a client can both publish and subscribe), it will need to initialize with both the subscribeKey and the publishKey.

You only need to supply the publishKey to clients that will publish (send) data to your application over the PubNub network. A read-only client for example would not need to initialize with this key.

 
Although a secretKey is also provided to you along with your publish and subscribe keys in the admin portal, it is not required for plain-old publish and subscribe. You'll only need the secretKey if you are using PAM functionality, which we discuss more in the PAM Feature Tutorial.
A message consists of a channel, and its associated data payload. A publishing client publishes messages to a given channel, and a subscribing client receives only the messages associated with the channels its subscribed to.

PubNub Message payloads can contain any JSON data including Booleans, Strings, Numbers, Arrays, and Objects. Simply publish the native type per your platform, and the clients will JSON serialize the data for you. Subscribers will automatically deserialize the JSON for you into your platform's associated native type.
 
When creating a message, keep these limits in mind:
  • Maximum message size is 32KB
  • The message size includes the channel name
  • The message size is calculated after all URL encoding and JSON serialization has occured. Depending on your data, this could add > 4X the original message size.

Keeping your messages < 1.5KB in size will allow them to fit into a single TCP packet!

For further details please check: https://support.pubnub.com/support/discussions/topics/14000006322

Channels are created on-the-fly, and do not incur any additional charges to use one or many in your application. When you create a PubNub application, all messages will be associated with a channel.

In a unicast (AKA 1:1) design pattern, the channels can be unique for each client in one-to-one communication. For example, user1 subscribes to user1-private, and user2 subscribes to user2-private. Using this pattern, each client listens on a channel which only relevant data to that client is sent. It has the advantage of minimal network usage (each client receives only the data it needs) and minimal processing (no need for filtering unneeded data).

PubNub Galaxy                                                                                                                            
In a multicast (AKA 1:Many) design pattern, a public (AKA system, global, or admin) channel is used for global communications amongst all clients. For example, building off our previous example, while a user can speak to any other user on their private channel, since each client in the application is listening on their private channel AND the public channel, they can receive on either. When receiving a message on the public channel, it may or may not be relevant for that particular receiving client -- to get around this, the client can filter on some sort of key, allowing them to selectively process messages with specific interest to them.

PubNub Pulse
In many cases, based on the use case of your application, the pattern you choose may be unicast, multicast, or a combination. There is no right or wrong pattern to implement, but based on your use case, there may be an optimal, most efficient pattern.
Since the text length of the channel name is counted as part of the entire message, and as such, as part of the maximum message length, it is best to keep the channel name as short as efficiency and utility allows.

Channel names are UTF-8 compatible. Prohibited chars in a channel name are:
  • comma: ,
  • slash: /
  • backslash: \
  • period: .
  • asterisks: *
  • colon: :
The procedure to publish and subscribe is really quite simple:

  • Include the PubNub library
  • PubNub - instantiate a PubNub instance.
  • subscribeToChannels - subscribe to a specific channel.
  • publish - send a message on a specific channel.
  • unsubscribeFromChannels() - additively unsubscribe to a specific channel.
Install CocoaPods gem by following the procedure defined under How to Get It.
To add the PubNub SDK to your project with CocoaPods, there are four basic tasks to complete which are covered below:
  1. Create new Xcode project.
  2. Create Podfile in new Xcode project root folder
    touch Podfile
  3. The PubNub client can be added as dynamic framework (only with a deployment target of iOS 8.0 and above) or as a static library (for deployment targets of iOS 7.0 and above).
    1. For a deployment target of iOS 8.0 and above use:
      source 'https://github.com/CocoaPods/Specs.git'
      
      # optionally complete and uncomment if compilation issues arise
      # project '<path to project relative to this Podfile>/<name of project without extension>'
      # workspace 'MyPubNubProject'
      
      use_frameworks!
       
      target 'application-target-name' do
          # Can only support iOS 8 or higher with frameworks, 
          # but can only target higher frameworks as well
          platform :ios, '8.0' # (or '9.0' or '10.0')
          pod "PubNub", "~> 4"
      end
    2. For a deployment target of iOS 7.0 and above use:
      source 'https://github.com/CocoaPods/Specs.git'
      
      # optionally complete and uncomment if compilation issues arise
      # project '<path to project relative to this Podfile>/<name of project without extension>'
      # workspace 'MyPubNubProject'
       
      target 'application-target-name' do
          # Should only use this with projects 
          # that must have a minimum deployment 
          # target of iOS 7 
          platform :ios, '7.0' # (if you don't need to use iOS 7, then see other Podfile)
          pod "PubNub", "~> 4"
      end
      If you have any other pods you'd like to include, or if you have other targets you'd to add (like a test target) add those entries to this Podfile as well. See the CocoaPods documentation for more information on Podfile configuration.
  4. Install your pods by running pod install via the command line from the directory that contains your Podfile.

 
After installing your Pods, you should only be working within the workspace generated by CocoaPods or specified by you in Podfile. Always open the newly generated workspace file, not the original project file!

To be able to use PubNub SDK within your application code you need to import it. Import PubNub SDK headers in implementation files for classes where you need to use it using this import statement:
#import <PubNub/PubNub.h>

Add the PNObjectEventListener protocol to AppDelegate in implementation file to anonymous category:
#import <PubNub/PubNub.h>

@interface AppDelegate () <PNObjectEventListener>

// Stores reference on PubNub client to make sure what it won't be released.
@property (nonatomic, strong) PubNub *client;

@end
If this PubNub instance will only be subscribing, you only need to pass the subscribeKey to initialize the instance. If this instance will be subscribing and publishing, you must also include the publishKey parameter.
PNConfiguration *configuration = [PNConfiguration configurationWithPublishKey:@"demo" 
																 subscribeKey:@"demo"];
self.client = [PubNub clientWithConfiguration:configuration];
The channel the messages will be published over is called my_channel. For this example, we will use the same instance to both publish and subscribe. To do this, we'll publish a message to the channel, but only after we're sure we've first successfully subscribed to the channel.

The publish and subscribeToChannels methods are pretty simple to use. For both publish and subscribeToChannels, the channel attribute defines the channel in use.

When using the subscribeToChannels method, the received messages are handles in the handler block:
/**
 Subscription process results arrive to listener which should adopt to PNObjectEventListener protocol
 and registered using:
 */
[self.client addListener:self]; 
[self.client subscribeToChannels: @[@"my_channel1", @"my_channel2"] withPresence:NO];
 

// Handle new message from one of channels on which client has been subscribed.
- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {
    // Reference to channel group to which belong chat where message has been sent.
    NSString *subscription = message.data.subscription; 
    NSLog(@"%@ sent message to '%@' at %@: %@", message.data.publisher, message.data.channel, 
			message.data.timetoken, message.data.message);
}

// Handle subscription status change.
- (void)client:(PubNub *)client didReceiveStatus:(PNStatus *)status {
    
	if (status.operation == PNSubscribeOperation) {
        
		// Check whether received information about successful subscription or restore.
		if (status.category == PNConnectedCategory || status.category == PNReconnectedCategory) {

			// Status object for those categories can be casted to `PNSubscribeStatus` for use below.
			PNSubscribeStatus *subscribeStatus = (PNSubscribeStatus *)status;
			if (subscribeStatus.category == PNConnectedCategory) {

				// This is expected for a subscribe, this means there is no error or issue whatsoever.
			}
			else {
                
				/**
				 This usually occurs if subscribe temporarily fails but reconnects. This means there was 
				 an error but there is no longer any issue.
				 */
			}
		}
		else if (status.category == PNUnexpectedDisconnectCategory) {

			/**
			 This is usually an issue with the internet connection, this is an error, handle 
			 appropriately retry will be called automatically.
			 */
		}
		// Looks like some kind of issues happened while client tried to subscribe or disconnected from 
		// network.
		else {

			PNErrorStatus *errorStatus = (PNErrorStatus *)status;
			if (errorStatus.category == PNAccessDeniedCategory) {

				/**
				 This means that PAM does allow this client to subscribe to this channel and channel group 
				 configuration. This is another explicit error.
				 */
			}
			else {
					
				/**
				 More errors can be directly specified by creating explicit cases for other error categories 
				 of `PNStatusCategory` such as: `PNDecryptionErrorCategory`,  
				 `PNMalformedFilterExpressionCategory`, `PNMalformedResponseCategory`, `PNTimeoutCategory`
				 or `PNNetworkIssuesCategory`
				 */
			}
		}
	}
	else if (status.operation == PNUnsubscribeOperation) {

		if (status.category == PNDisconnectedCategory) {

			/**
			 This is the expected category for an unsubscribe. This means there was no error in unsubscribing
			 from everything.
			 */
		}
	}
	else if (status.operation == PNHeartbeatOperation) {

		/**
		 Heartbeat operations can in fact have errors, so it is important to check first for an error.
		 For more information on how to configure heartbeat notifications through the status 
		 PNObjectEventListener callback, consult http://www.pubnub.com/docs/ios-objective-c/api-reference-configuration#configuration_basic_usage
		 */

		if (!status.isError) { /* Heartbeat operation was successful. */ } 
		else { /* There was an error with the heartbeat operation, handle here. */ }
	}
}
 
During your application's lifecycle, you can call subscribeToChannels() repeatedly to additively subscribe to additional channels.
For publish, the message attribute contains the data you are sending.
[self.client publish: @{@"Dictionary": @[@"with",@"array",@"as", @"value"]} 
		   toChannel: @"pubnub" withCompletion:^(PNPublishStatus *status) {
    
	if (!status.isError) {

		// Message successfully published to specified channel.
	}
	else {

		/**
		 Handle message publish error. Check 'category' property to find 
		 out possible reason because of which request did fail.
		 Review 'errorData' property (which has PNErrorData data type) of status
		 object to get additional information about issue.

		 Request can be resent using: [status retry];
		 */
	}
}];
The above code demonstrates how to subscribe, and how to publish. But what if your use-case requires that client instance not only subscribes and publishes, but also that its guaranteed to start publishing only AFTER it’s successfully subscribed? -- In other words, you want to guarantee it receives all of its own publishes?
The Objective-C client SDK, like many of the PubNub SDKs, is asynchronous -- publish can, and most likely will, fire before the previously executed subscribeToChannels call completes. The result is, for a single-client instance, you would never receive (via subscribing) the message you just published, because the subscribe operation did not complete before the message was published.
To get around this common case, we can take advantage of the optional connect callback in the subscribe method.
- (void)client:(PubNub *)client didReceiveStatus:(PNStatus *)status {
 
	if (status.operation == PNSubscribeOperation) {
        
		// Check whether received information about successful subscription or restore.
		if (status.category == PNConnectedCategory || status.category == PNReconnectedCategory) {

			// Status object for those categories can be casted to `PNSubscribeStatus` for use below.
			PNSubscribeStatus *subscribeStatus = (PNSubscribeStatus *)status;
			if (subscribeStatus.category == PNConnectedCategory) {

				// This is expected for a subscribe, this means there is no error or issue whatsoever.

				[self.client publish: @"Hello from the PubNub Objective-C SDK" 
						   toChannel: [client channels].lastObject
					  withCompletion:^(PNPublishStatus *publishStatus) {
 
					// Check whether request successfully completed or not.
					if (!publishStatus.isError) {
 
						// Message successfully published to specified channel.
					}
					// Request processing failed.
					else {
 
						/**
						 Handle message publish error. Check 'category' property to find out 
						 possible reason because of which request did fail.
						 Review 'errorData' property (which has PNErrorData data type) of status
						 object to get additional information about issue.
				
						 Request can be resent using: [publishStatus retry];
						 */
					}
				}];
			}
			else {
                
				/**
				 This usually occurs if subscribe temporarily fails but reconnects. This means there was 
				 an error but there is no longer any issue.
				 */
			}
		}
		else if (status.category == PNUnexpectedDisconnectCategory) {

			/**
			 This is usually an issue with the internet connection, this is an error, handle 
			 appropriately retry will be called automatically.
			 */
		}
		// Looks like some kind of issues happened while client tried to subscribe or disconnected from 
		// network.
		else {

			PNErrorStatus *errorStatus = (PNErrorStatus *)status;
			if (errorStatus.category == PNAccessDeniedCategory) {

				/**
				 This means that PAM does allow this client to subscribe to this channel and channel group 
				 configuration. This is another explicit error.
				 */
			}
			else {
					
				/**
				 More errors can be directly specified by creating explicit cases for other error categories 
				 of `PNStatusCategory` such as: `PNDecryptionErrorCategory`,  
				 `PNMalformedFilterExpressionCategory`, `PNMalformedResponseCategory`, `PNTimeoutCategory`
				 or `PNNetworkIssuesCategory`
				 */
			}
		}
	}
}
By following this pattern on a client that both subscribes and publishes when you want to be sure to subscribe to your own publishes, you’ll never miss receiving a message.
While you are subscribed to a channel, you will continue to receive messages published to that channel. To stop receiving messages on a given channel, you must -unsubscribeFromChannels:withPresence: from the channel.
/**
 Unsubscription process results arrive to listener which should adopt to
 PNObjectEventListener protocol and registered using:
 */
[self.client addListener:self];
[self.client unsubscribeFromChannels: @[@"my_channel1", @"my_channel2"] withPresence:NO];

// Handle subscription status change.
- (void)client:(PubNub *)client didReceiveStatus:(PNStatus *)status {
 
	if (status.operation == PNUnsubscribeOperation && status.category == PNDisconnectedCategory) {

		/**
		 This is the expected category for an unsubscribe. This means there was no error in 
		 unsubscribing from everything.
		 */
	}
}
Like -subscribeToChannels:withPresence:, -unsubscribeFromChannels:withPresence: can be called multiple times to successively remove different channels from the active subscription list.
 Unsubscribing from all the channel(s) and then subscribing to a new channel Y is not the same than Subscribing to the channel Y and then unsubscribing from the previously subscribe channel(s). Unsubscribing from all the channels resets the timetoken and thus, there could be some gaps in the subscription that may lead to a message loss.