Programming4us
         
 
 
Programming

iPhone SDK : Creating Basic GameKit Services (part 2) : Sending and Receiving Data

11/13/2010 5:18:01 PM

Sending and Receiving Data

The data handler (in this case, self) must implement the receiveData:fromPeer:inSession:context: method. The data sent to this method uses an NSData object; there are no hooks or handles for partial data receipt and processing. As the data arrives as a single chunk, keep your data bursts short (under 1,000 bytes) and to the point for highly interactive applications.

- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer
inSession: (GKSession *)session context:(void *)context
{
// handle data here
}

Send data via the session object. You can send in reliable mode or unreliable mode. Reliable mode uses error checking and retrying until the data is properly sent. All items are guaranteed to arrive in the order they are sent, using TCP transmission. With unreliable mode, data is sent once using UDP transmission, with no retry, Data may arrive out of order. Use reliable mode (GKSendDataReliable) when you must guarantee correct delivery and unreliable mode for short bursts of data that must arrive nearly instantaneously.

- (void) sendDataToPeers: (NSData *) data
{
// Send the data, checking for success or failure
NSError *error;
BOOL didSend = [self.session sendDataToAllPeers:data
withDataMode:GKSendDataReliable error:&error];
if (!didSend)
NSLog(@"Error sending data to peers: %@",
[error localizedDescription]);
}

As a rule, the one error you’ll encounter here results from queuing too much data in reliable mode. This produces a “buffer full” error.

State Changes

The following session delegate callback lets you know when a peer’s state has changed. The two states you want to look for are connected, that is, when the connection finally happens after the peer picker has been dismissed, and disconnected, when the other user quits the application, manually disconnects, or moves out of range.

- (void)session:(GKSession *)session peer:(NSString *)peerID
didChangeState:(GKPeerConnectionState)state
{
/* STATES:
GKPeerStateAvailable, = 0,
GKPeerStateUnavailable, = 1,
GKPeerStateConnected, = 2,
GKPeerStateDisconnected, = 3,
GKPeerStateConnecting = 4 */

if (state == GKPeerStateConnected)
{
// handle connected state
}

if (state == GKPeerStateDisconnected)
{
// handle disconnection
}
}

To force a session to disconnect, use the disconnectFromAllPeers method.

- (void) disconnect
{
// Disconnect and then reset the session property
[self.session disconnectFromAllPeers];
self.session = nil;
}

Creating a GameKit Helper

Recipe 12-1 bundles the entire peer process into a simplified helper class. This class hides most of the GameKit details connection and data transfer details, while providing a demonstration of how to use these features. More importantly, it breaks down how you might look at the GameKit process, with its two key details: connection and data.

Connecting

Any GameKit client you write must respond appropriately to the current connection state. You need to be able to establish that connection and respond when it goes live or when it drops. This class provides both connect and disconnect requests. For the most part, monitoring connections involves toggling a state Boolean (isConnected) and updating any buttons that control a connect/disconnect toggle.

To simplify these updates, the class allows you to assign a view controller (via the viewController property) and automatically updates the right-hand navigation item button. The button starts off as Connect, and when tapped disappears until the user cancels or a connection is fully established. After connecting, the button updates to Disconnect and provides a callback to the helper’s disconnect method.

Handling Data

By providing the connection state details for you, you can use this GameKitHelper class to create simple GameKit-enabled applications. The data handling, however, remains in your hands. Consider the following snippet. It shows the entire implementation for a chat application view controller, demonstrating the data transfer methods for this app.

@implementation TestBedViewController
- (void)textViewDidChange:(UITextView *)textView
{
// Perform updates only when connected
if (![GameKitHelper sharedInstance].isConnected) return;

NSString *text = sendView.text;

// Check for empty text. If so, send special clear request
if (!text || (text.length == 0)) text = @"xyzzyclear";
NSData *textData = [text dataUsingEncoding:NSUTF8StringEncoding];
[GameKitHelper sendData:textData];
}

-(void) receivedData: (NSData *) data
{
NSString *text = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];

// Check for clear request when updating text
receiveView.text = [text isEqualToString:@"xyzzyclear"] ?
@"" : text;
}

- (void) clear
{
// Handle a clear request
sendView.text = @"";
}

- (void) viewDidLoad
{
self.navigationItem.leftBarButtonItem = BARBUTTON(@"Clear",
@selector(clear));

// Initialize the helper
[GameKitHelper sharedInstance].sessionID = @"Typing Together";
[GameKitHelper sharedInstance].dataDelegate = self;
[GameKitHelper assignViewController:self];

// Present the keyboard
[sendView becomeFirstResponder];
}
@end

As you can see, this application monitors a “send” text view, and when it changes (as the user types), sends the contents of that view through GameKit to a peer. At the same time, it waits for data, and when it receives it, updates the received text view to show what the peered user has typed. A Clear button erases the “send” view text.

This application demonstrates the second half of the GameKit problem, handling data. Recipe 12-1’s helper class creates a data delegate protocol, which is subscribed to by this text chat view controller. Data is passed along through the custom receivedData: delegate method, allowing the received text view to update with text typed on the peer device.

Similarly, the text view delegate method textViewDidChange: passes on responsibility for transmitting the actual text to the GameKitHelper class, calling the sendData: method to convey the data to connected peers.

Note

Recipe 12-1 does not address the issue of out-of-order packet receipt. See Apple’s GKTank sample code for an example of network packet handling. Apple’s code looks for the last packet time and the packet ID to ensure that packets are handled in the proper sequence.


The Helper Class

Recipe 1 contains the implementation for the GameKitHelper class. The associated sample code for this recipe shows the class in action, creating the text chat application discussed previously. This class was designed for reuse and can easily be decoupled from the text chat and repurposed, as you see in the next recipe.

Recipe 1. GameKitHelper Class
@implementation GameKitHelper
@synthesize dataDelegate;
@synthesize viewController;
@synthesize sessionID;
@synthesize session;
@synthesize isConnected;
// Macro helps check and then send selectors for data
// delegate callbacks
#define DO_DATA_CALLBACK(X, Y) if (self.dataDelegate && \
[self.dataDelegate respondsToSelector:@selector(X)]) \
[self.dataDelegate performSelector:@selector(X) withObject:Y];

#pragma mark Shared Instance

static GameKitHelper *sharedInstance = nil;
+ (GameKitHelper *) sharedInstance
{
if(!sharedInstance) sharedInstance = [[self alloc] init];
return sharedInstance;
}

#pragma mark Data Sharing

// Send data to all connected peers
- (void) sendDataToPeers: (NSData *) data
{
NSError *error;
BOOL didSend = [self.session sendDataToAllPeers: data
withDataMode:GKSendDataReliable error:&error];
if (!didSend)
DO_DATA_CALLBACK(sentData:,
(didSend ? nil : [error localizedDescription]));
}

// Redirect data receipt to the data delegate
- (void) receiveData:(NSData *)data fromPeer:(NSString *)peer
inSession: (GKSession *)session context:(void *)context
{
DO_DATA_CALLBACK(receivedData:, data);
}

#pragma mark Connections

// Start a new connection by presenting a peer picker
- (void) startConnection
{
if (!self.isConnected)
{
GKPeerPickerController *picker = [[GKPeerPickerController
alloc] init];
picker.delegate = self;
picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby;
[picker show];
if (self.viewController)
self.viewController.navigationItem.rightBarButtonItem =
nil;
}
}

// Dismiss the peer picker on cancel
- (void) peerPickerControllerDidCancel:
(GKPeerPickerController *)picker
{
[picker release];
if (self.viewController)
self.viewController.navigationItem.rightBarButtonItem =
BARBUTTON(@"Connect", @selector(startConnection));
}

// Upon a successful connection, set up the data handler
- (void)peerPickerController:(GKPeerPickerController *)picker
didConnectPeer:(NSString *)peerID
toSession: (GKSession *) session
{
[picker dismiss];
[picker release];
isConnected = YES;
[self.session setDataReceiveHandler:self withContext:nil];
DO_DATA_CALLBACK(connectionEstablished, nil);
}

// Provide the session information including id and mode
- (GKSession *)peerPickerController:(GKPeerPickerController *)picker
sessionForConnectionType:(GKPeerPickerConnectionType)type
{
if (!self.session) {
self.session = [[GKSession alloc] initWithSessionID:
(self.sessionID ? self.sessionID : @"Sample Session")
displayName:nil sessionMode:GKSessionModePeer];
self.session.delegate = self;
}
return self.session;
}

#pragma mark Session Handling

// Disconnect the current session
- (void) disconnect
{
[self.session disconnectFromAllPeers];
self.session = nil;
}

// Detect when the other peer has changed its state
- (void)session:(GKSession *)session peer:(NSString *)peerID
didChangeState:(GKPeerConnectionState)state
{
if (state == GKPeerStateConnected)
{
if (self.viewController)
self.viewController.navigationItem.rightBarButtonItem =
BARBUTTON(@"Disconnect", @selector(disconnect));
}

if (state == GKPeerStateDisconnected)
{
self.isConnected = NO;
showAlert(@"Lost connection with peer. You are no longer \
connected to another device.");
[self disconnect];
if (self.viewController)
self.viewController.navigationItem.rightBarButtonItem =
BARBUTTON(@"Connect", @selector(startConnection));
}
}

// Utility method for setting up the view controller
- (void) assignViewController: (UIViewController *) aViewController
{
self.viewController = aViewController;
self.viewController.navigationItem.rightBarButtonItem =
BARBUTTON(@"Connect", @selector(startConnection));
}

#pragma mark Class utility methods

// These class methods redirect to instance methods.
// They're here for convenience only
+ (void) connect
{
[[self sharedInstance] startConnection];
}

+ (void) disconnect
{
[[self sharedInstance] disconnect];
}

+ (void) sendData: (NSData *) data
{
[[self sharedInstance] sendDataToPeers:data];
}

+ (void) assignViewController: (UIViewController *) aViewController
{
[[self sharedInstance] assignViewController:aViewController];
}
@end
Other -----------------
- iPad : Navigating with Maps
- Adding iPad to the Mix
- A Brief History of Legacy .NET Distributed Technologies : .NET Remoting
- A Brief History of Legacy .NET Distributed Technologies : .NET Enterprise Services
- iPad SDK : Outputting to an External Screen
- iPad SDK : Displaying Multiple Videos
- Parallel Programming Drivers
- Parallel Programming with Microsoft .Net : Parallel Loops - An Example
- Parallel Programming with Microsoft .Net : Parallel Loops - The Basics
- What is New in iPhone SDK 3.2 for the iPad (part 2)
- What is New in iPhone SDK 3.2 for the iPad (part 1)
- Programming with DirectX : Rendering Geometry - Colors
- ASP.NET Security : The Membership and Role Management API (part 3) - Role
- ASP.NET Security : The Membership and Role Management API (part 2) - Provider
- ASP.NET Security : The Membership and Role Management API (part 1)
- ASP.NET Security : Security-Related Controls (part 2)
- ASP.NET Security : Security-Related Controls (part 1)
- WCF Security Concepts
- Certificate-Based Encryption
- Encryption Using SSL
 
 
Most View
- Performing SharePoint 2010 Installations (part 2)
- Keeping Windows 7 and Other Software Up to Date : Windows Update
- SOA with .NET and Windows Azure : Service Contracts with WCF (part 1)
- Working with the SharePoint 2010 Management Shell (part 2) - Understanding cmdlets
- SQL Server 2008 High Availability : The Fundamentals of HA
- Programming WCF Services : The Response Service (part 3) - Queued Service-Side Programming & Response Service-Side Programming
- Developing an SEO-Friendly Website : Root Domains, Subdomains, and Microsites (part 1)
- Exchange Server 2003 : Virtual Servers - Configuring Authentication
- A Technical Overview of the Mobile Web : THE TECHNICAL CHALLENGES OF MOBILE DEVICES (part 2)
- Working with the SharePoint 2010 Management Shell (part 4) - Understanding Properties and Methods
Top 10
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 3) - Configuring Recipient Filtering
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 2)
- Implementing Edge Services for an Exchange Server 2007 Environment : Utilizing the Basic Sender and Recipient Connection Filters (part 1)
- Implementing Edge Services for an Exchange Server 2007 Environment : Installing and Configuring the Edge Transport Server Components
- What's New in SharePoint 2013 (part 7) - BCS
- What's New in SharePoint 2013 (part 6) - SEARCH
- What's New in SharePoint 2013 (part 6) - WEB CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 5) - ENTERPRISE CONTENT MANAGEMENT
- What's New in SharePoint 2013 (part 4) - WORKFLOWS
- What's New in SharePoint 2013 (part 3) - REMOTE EVENTS