CocoaCocoaiOSObjective-CCocoa Stream Controller Tutorial for Realtime Apps

 
Requires that the Stream Controller add-on is enabled for your key. How do I enable add-on features for my keys? - see http://www.pubnub.com/knowledge-base/discussion/644/how-do-i-enable-add-on-features-for-my-keys
When enabled via the PubNub Admin Console, the Stream Controller feature provides PubNub developers the ability to efficiently subscribe to multiple channels via Channel Multiplexing (MXing) and Channel Groups.
Channel and Channel Group names are UTF-8 compatible. Name length is limited to 92, and prohibited chars in a channel group are:
  • comma: ,
  • slash: /
  • backslash: \
  • period: .
  • asterisks: *
  • colon: :
Channel Multiplexing enables developers to subscribe to up to 50 channels (not within a channel group) over a single TCP socket. On mobile devices, its easy to realize the network bandwidth and battery power savings gained from channel multiplexing.
 
These code samples build off code defined in the Pub & Sub tutorial, so before proceeding, be sure you have completed the Pub & Sub tutorial first.
The examples below demonstrate how to subscribe and unsubscribe from multiple channels with a single call. The use of callbacks will give you a way to know when your operations complete successfully or with errors.
/**
 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 {

	// Handle new message stored in message.data.message
	if (![message.data.channel isEqualToString:message.data.subscription]) {
 
		// Message has been received on channel group stored in message.data.subscription.
	}
	else {
 
		// Message has been received on channel stored in message.data.channel.
	}

	NSLog(@"Received message: %@ on channel %@ at %@", message.data.message,
		  message.data.channel, message.data.timetoken);
}

// 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`
				 */
			}
		}
	}
}
/**
 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.
		 */
	}
}
Channel Groups allows PubNub developers to bundle thousands of channels into a group that can be identified by name. These Channel Groups can then be subscribed to, receiving data from the many backend-channels the channel group contains.
When building with channel-groups, you may create an unlimited number of these uniquely named channel groups, each with up to 2000 channels in them. Up to 10 channel groups may be subscribed to per PubNub instance. The below diagram depicts this design pattern and default limits:
Channel Groups
As an example, for a group chat application, the channel group name would describe the conversation, and the channel names would be the chat audiences’ channels.
For a user rajat: family, friends and work are the chat room names (channel groups) and the channels are the contact’s (channels), your design may look something like this:
  • rajat : family : wife
  • rajat : family : daughter
  • rajat : family : son
  • rajat : friends : pandu
  • rajat : friends : craig
  • rajat : friends : stephen
  • rajat : work : pandu
  • rajat : work : stephen
  • rajat : work : sergey
Given the above design pattern family, friends and work are the 3 channel groups, which each contain a handful of channels in different combinations such as wife, son, pandu, craig, stephen, etc. Similarly for user alex: mychats is the chat room name (channel group) and the channels general-a-messages, general-a-keyboard-presence, general-j-messages, general-j-keyboard-presence, general-m-messages and general-m-keyboard-presence, your design may look something like this:
  • alex : mychats : general-a-messages
  • alex : mychats : general-a-keyboard-presence
  • alex : mychats : general-j-messages
  • alex : mychats : general-j-keyboard-presence
  • alex : mychats : general-m-messages
  • alex : mychats : general-m-keyboard-presence
Please contact support@pubnub.com if you feel this design pattern and/or default limits will not accommodate your application's design.
 
Each Channel Group and associated channel(s) is identified by a unique name. These names may be a string of up to 64 Unicode characters, excluding reserved characters: , , : , . , / , *, non-printable ASCII control characters and Unicode zero.
Before you use a channel group, you must first add a channel to it. Channels can be added from either a client-side (mobile phone, web page), or a server-side (Java, Ruby, Python) device.

For our example, lets create a chat application around channel groups. Each channel group will contain the members' topic channels for the group. The following steps will demonstrate:
  1. Defining the Channel Group Message and Error Callback
  2. Defining the Channel Group
  3. Adding Channels to a Channel Group
  4. Subscribing to the Channel Group
  5. Unsubscribing to the Channel Group
  6. Receiving Channel Group Presence Messages
  7. Removing Channels from a Channel Group
  8. Listing all Channels in a Channel Group
  9. Removing a Channel from a Channel Group

We'll first create a Channel Group (CG) error and message callback.
// Handle new message from one of channels on which client has been subscribed.
- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {

	// Handle new message stored in message.data.message
	if (![message.data.channel isEqualToString:message.data.subscription]) {
 
		// Message has been received on channel group stored in message.data.subscription.
	}
	else {
 
		// Message has been received on channel stored in message.data.channel.
	}

	NSLog(@"Received message: %@ on channel %@ at %@", message.data.message,
		  message.data.channel, message.data.timetoken);
}

// 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`
				 */
			}
		}
	}
}
Next, we'll implicitly define a channel group called family, and define some default callbacks and names.
NSString *channelGroup = @"family";
We'll add the channel wife to the channel group channel_group.
[self.client addChannels: @[@"wife"] toGroup:channelGroup
		  withCompletion:^(PNAcknowledgmentStatus *status) {

	if (!status.isError) {

		// Handle successful channels list modification for group.
	}
	else {

		/**
		 Handle channels list modification for group 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];
		 */
	}
}];
Continuing creation of the CG, we add the son and daughter channels to this channel group as well.
[self.client addChannels: @[@"son", @"daughter"] toGroup:channelGroup
		  withCompletion:^(PNAcknowledgmentStatus *status) {

	if (!status.isError) {

		// Handle successful channels list modification for group.
	}
	else {

		/**
		 Handle channels list modification for group 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];
		 */
	}
}];
Now that we've added some channels to our CG, we need to subscribe to it. When a message arrives published to any of the channel group members, the message will arrive via the m variable in the message callback. The c variable will contain the source channel name.
/**
 Subscription process results arrive to listener which should adopt to 
 PNObjectEventListener protocol and registered using:
 */
[self.client addListener:self];
[self.client subscribeToChannelGroups:@[channelGroup] withPresence:NO];


// Handle new message from one of channels on which client has been subscribed.
- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {

	// Handle new message stored in message.data.message
	if (![message.data.channel isEqualToString:message.data.subscription]) {
 
		// Message has been received on channel group stored in message.data.subscription.
	}
	else {
 
		// Message has been received on channel stored in message.data.channel.
	}

	NSLog(@"Received message: %@ on channel %@ at %@", message.data.message,
		  message.data.channel, message.data.timetoken);
}

// 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`
				 */
			}
		}
	}
}
To stop receiving messages on a channel group you need to unsubscribe from the group:
/**
 Unsubscription process results arrive to listener which should adopt to
 PNObjectEventListener protocol and registered using:
 */
[self.client addListener:self];
[self.client unsubscribeFromChannelGroups: @[@"developers"] withPresence:YES];


// 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.
		 */
	}
}
To enable receiving Presence messages for the CG, just set withPresence to true in subscribeToChannelGroups :
/**
 Subscription process results arrive to listener which should adopt to
 PNObjectEventListener protocol and registered using:
 */
[self.client addListener:self];
[self.client subscribeToChannelGroups:@[channelGroup] withPresence:YES];


// Handle new message from one of channels on which client has been subscribed.
- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {

	// Handle new message stored in message.data.message
	if (![message.data.channel isEqualToString:message.data.subscription]) {
 
		// Message has been received on channel group stored in message.data.subscription.
	}
	else {
 
		// Message has been received on channel stored in message.data.channel.
	}

	NSLog(@"Received message: %@ on channel %@ at %@", message.data.message,
		  message.data.channel, message.data.timetoken);
}

// New presence event handling.
- (void)client:(PubNub *)client didReceivePresenceEvent:(PNPresenceEventResult *)event {
	
	if (![event.data.channel isEqualToString:event.data.subscription]) {
		
		// Presence event has been received on channel group stored in event.data.subscription.
	}
	else {
		
		// Presence event has been received on channel stored in event.data.channel.
	}
	
	if (![event.data.presenceEvent isEqualToString:@"state-change"]) {
		
		NSLog(@"%@ \"%@'ed\"\nat: %@ on %@ (Occupancy: %@)", event.data.presence.uuid, 
			  event.data.presenceEvent, event.data.presence.timetoken, event.data.channel, 
			  event.data.presence.occupancy);
	}
	else {
		
		NSLog(@"%@ changed state at: %@ on %@ to: %@", event.data.presence.uuid, 
			  event.data.presence.timetoken, event.data.channel, event.data.presence.state);
	}
}

// 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`
				 */
			}
		}
	}
}
We'll learn more about using PubNub Presence features in the Presence tutorial.
Lets assume that our son no longer wants to be part of the family chat app, because he is getting irritated with his sister's comments. To remove his channel from the group, we'd just call the following method:
[self.client removeChannels:@[@"son"] fromGroup:channelGroup
			 withCompletion:^(PNAcknowledgmentStatus *status) {

	if (!status.isError) {

		// Handle successful channels list modification for group.
	}
	else {

		/**
		 Handle channels list modification for group 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];
		 */
	}
}];
If he had permissions to remove his sister from the group, he probably would just do that instead, but if PAM was implemented, we could make it so that he only has permissions to either add or remove himself to the group, not add or remove others. We'll discuss PAM in more detail in the PAM, SSL, and AES256 Message Encryption tutorial.
To get a list of all the channels defined within a channel group, call the following method. In this example, we dump all the channels within rajat:family:
[self.client channelsForGroup:channelGroup withCompletion:^(PNChannelGroupChannelsResult *result,
															PNErrorStatus *status) {

	if (!status) {

		// Handle downloaded list of chanels using: result.data.channels
	}
	else {

		/**
		 Handle channels for group audition 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];
		 */
	}
}];
To remove a Channel Group by name, use the following method:
[self.client removeChannelsFromGroup:channelGroup withCompletion:^(PNAcknowledgmentStatus *status) {

	if (!status.isError) {

		// Handle successful channel group removal.
	}
	else {

		/**
		 Handle channel group removal 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];
		 */
	}
}];