1. Trang chủ
  2. » Công Nghệ Thông Tin

Praise for The iPhone Developer’s Cookbook 2nd phần 7 pdf

88 263 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 88
Dung lượng 11,44 MB

Nội dung

ptg 499 Recipe: Creating Basic GameKit Services Figure 12-2 This is the first screen presented to the user for peer-to-peer Bluetooth connections. where a user selects between Online and Nearby modes.When presented, it shows the interface in Figure 12-2.You do not have to use a peer picker to establish GameKit ses- sions.The iPhone SDK now lets you create your own custom interfaces to work with the underlying GameKit connections.A sample that demonstrates how to do so has been added to the sample code that accompanies this chapter. // Create and present a new peer picker GKPeerPickerController *picker = [[[GKPeerPickerController alloc] init]; picker.delegate = self; picker.connectionTypesMask = GKPeerPickerConnectionTypeNearby; [picker show]; When your mask includes the online type as well (GKPeerPickerConnectionTypeOnline), the picker first asks the user which kind of connection to use before moving on to either the nearby connection interface of Figure 12-2 or to a custom online interface that you must build yourself. Pressing Cancel Users may cancel out of the peer picker alert.When they do so, the delegate receives a peerPickerControllerDidCancel: callback. If you display a “connect” button in your application, make sure to restore it at this point so the user can try again. ptg Creating the Session Object As the picker delegate, you must supply a session object on request. Sessions, which pro- vide an abstract class that creates and manages a data socket between devices, belong to the GKSession class and must be initialized with a session identifier.This is the unique string used to create the Bonjour service and link together two iPhone devices (peers) both advertising the same service. By setting the display name to nil, the session uses the built-in device name. - (GKSession *)peerPickerController:(GKPeerPickerController *)picker sessionForConnectionType:(GKPeerPickerConnectionType)type { // Create a new session if one does not already exist if (!self.session) { self.session = [[[GKSession alloc] initWithSessionID: (self.sessionID ? self.sessionID : @"Sample Session") displayName:nil sessionMode:GKSessionModePeer] autorelease]; self.session.delegate = self; } return self.session; } Although this is an optional method, you’ll usually want to implement it so you can set your session ID and mode. Upon detecting another iPhone or iPod with the same adver- tised service ID, the peer picker displays the peer as a compatible match, as shown in Figure 12-3. Waiting for the peer picker list can take a few seconds or up to a few minutes. During development, you usually need to allow your Bonjour network stack to clear out any pre- vious sessions when you iterate on the code.That’s what typically causes the longer delays.Apple recommends always debugging from a clean restart. If debugging delays get frustrating enough, make sure to reboot. In normal use, connection delays usually hover around 45 seconds at a maximum. Warn your users to be patient. In Figure 12-3, Binky is the device name for a second iPhone running the same application.When the user taps the name Binky, this iPhone automatically goes into client mode, and Binky goes into server mode. Client and Server Modes When a device changes into client mode, it stops advertising its service.The Choose an iPhone or iPod Touch dialog shown previously in Figure 12-3 changes on the server unit. The client’s peer name dims to dark gray and the words “is not available” appear under- neath.A few seconds later (and this can actually run up to a minute, so again warn your users about delays), both units update their peer picker display. Figure 12-4 shows the server and client peer pickers during this process.The client waits as the server receives the connection request (left). On the server, the host user must 500 Chapter 12 Making Connections with GameKit and Bonjour ptg Figure 12-3 The peer picker lists all devices that can act as peers. Figure 12-4 Upon choosing a partner, the client goes into wait mode (left) as the server decides whether to accept or decline the connection (middle). Should the server decline, the client receives a notice to that effect (right). 501 Recipe: Creating Basic GameKit Services accept or decline the connection (middle). Should they decline, an updated peer picker notifies the client (right). If they accept, both delegates receive a new callback. ptg The delegate callback lets the new peers dismiss the peer picker and to set their data received handler. Make sure to release the picker at this time. - (void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(NSString *)peerID toSession: (GKSession *) session { // Dismiss and release the picker, then set the data handler [picker dismiss]; [picker release]; [self.session setDataReceiveHandler:self withContext:nil]; } 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. Reli- able 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 deliv- ery 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 reli- able mode.This produces a “buffer full” error. 502 Chapter 12 Making Connections with GameKit and Bonjour ptg 503 Recipe: Creating Basic GameKit Services 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 ptg when it drops.This class provides both connect and disconnect requests. For the most part, monitoring connections involves toggling a state Boolean (isConnected) and updat- ing 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 can- cels or a connection is fully established.After connecting, the button updates to Discon- nect 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 504 Chapter 12 Making Connections with GameKit and Bonjour ptg { 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. 505 Recipe: Creating Basic GameKit Services The Helper Class Recipe 12-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 dis- cussed 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 12-1 GameKitHelper Class @implementation GameKitHelper @synthesize dataDelegate; @synthesize viewController; @synthesize sessionID; @synthesize session; @synthesize isConnected; ptg // 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; 506 Chapter 12 Making Connections with GameKit and Bonjour ptg 507 Recipe: Creating Basic GameKit Services [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 { ptg [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]; 508 Chapter 12 Making Connections with GameKit and Bonjour [...]... Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 12 and open the project for this recipe Recipe: Creating a Mac Client for an iPhone Bonjour Service Apple’s Bonjour sample code works with both iPhone and Macintosh applications... Pasteboarding Recipe 12 -7 provides several checks before sending, retrieving, and copying pasteboard data Users must confirm that they intend to share data of a given type.When receiving data, they must authorize the application to copy the data to the general system pasteboard.This approach ensures that proactive user effort must take place before performing these actions Recipe 12 -7 Sharing the iPhone Pasteboard... containing all of the sample code from the book, go to the folder for Chapter 12 and open the project for this recipe Recipe: Peeking Behind the Scenes At the time of writing, GameKit logs its status information as it runs, mostly by NSLog calls introduced by Apple’s engineers.You can track this information at the debug console, or you can use the following trick to redirect it to a file (the messages will... to the CFNetwork framework, replacing that with the AppKit framework for Mac Recipe 12-6 provides a Macintosh client to demonstrate how to use the client side of the Bonjour sample code This meshes with the server from Recipe 12-5 There’s nothing intrinsically specific about the roles and the platforms chosen.A Mac could just as easily provide a service and an iPhone its client Since an iPhone/ iPhone... using dataForPasteboardType:.This returns the data from the first item whose type matches the one sent as the parameter.Any other matching items in the pasteboard are ignored Should you need to retrieve all matching data, recover an itemSetWithPasteboardTypes: and then iterate through the set to retrieve each dictionary Recover the data type for each item from the single dictionary key and the data... Code To get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 12 and open the project for this recipe Recipe: Using Bonjour to Create an iPhone Server Although GameKit is built around Bonjour, sometimes you’ll want to use Bonjour directly For example,... http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 12 and open the project for this recipe Recipe: iPhone to iPhone Gaming Via BonjourHelper If you’re willing to forgo GameKit’s Bluetooth and work with WiFi, you can duplicate many of GameKit’s features on all iPhones including the older first generation... Recipe’s Code To get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 12 and open the project for this recipe Recipe: Working Around Real-World GameKit Limitations Although GameKit is built on Bonjour, it isn’t meant to provide the same kind of general... note of the way this recipe updates the content offset for the text view It ensures that the text at the bottom of the view is always displayed after an update It does this by setting the offset to one page height shorter than the full content size Recipe 12-2 Monitoring GameKit @implementation TestBedViewController @synthesize textView; - (void) listenForStderr: (NSTimer *) timer; { // Monitor the stderr... accepts the connection and sets its TCPConnection delegate .The difference between the server delegate and the connection delegate is that the server is responsible for listening for new connections, and the connection is responsible for sending and receiving data 515 516 Chapter 12 Making Connections with GameKit and Bonjour As with GameKit, the user decides whether to accept a new connection .The connection . GameKit Services The Helper Class Recipe 12-1 contains the implementation for the GameKitHelper class .The associated sample code for this recipe shows the class in action, creating the text chat. get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for. get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for

Ngày đăng: 13/08/2014, 18:20

TỪ KHÓA LIÊN QUAN