Multipeer Connectivity Framework - Lost Peer stays in Session - ios

I wonder if this Multipeer Connectivity framework is ready for use in the real world, given all the bugs that have been encountered by the community. I think I'm setting it up right, but all the other sample projects I've tried encounter similar issues.
The problem I'm having may be tied to some issue inherent to Bonjour or something, I can't figure it out, but basically the problem is as follows:
I have an active MCSession with a number of peers.
Now, if a device is in a session, and then force quits out, that "Peer" stays connected for an indefinite amount of time.
There's nothing I can do to force that user out, even though the browser:lostPeer: method is called for that peer and is no longer even showing up in the browser as
"Nearby".
The session:peer:didChangeState: method is not called for that peer.
When that peer that force quitted comes back to the app, they are "Found" again by the browser:foundPeer:withDiscoveryInfo: but still also exist in the session.connectedPeers NSArray. Obviously they don't receive any data or updates about the session still and are not actually connected.
The only thing that seems to work to register that original peer as MCSessionStateNotConnected to the session is by reconnecting that peer to the original session. Then there is a duplicate call to session:peer:didChangeState: where the new instance of the peerID is MCSessionStateConnected and shortly after the old instance of the peerID calls with MCSessionStateNotConnected.
The sample chat application demonstrates this issue well: https://developer.apple.com/library/ios/samplecode/MultipeerGroupChat/Introduction/Intro.html
Since there doesn't seem to be any way to manually force remove a peer from the session, what should I do? Should I try and rebuild the session somehow?
This Framework seems like a bit of a mess, but I'm trying to reserve judgement!

My only workaround to this type of issue has been to have a 1-1 relationship between sessions and peers. It complicates the sending of broadcasts, but at least allows for peer-level disconnects and cleanup through disconnecting/removing the session itself.
Update
To elaborate on my original answer, in order to be able to send data to connected peers it's necessary to maintain a reference to the session that was created for each peer. I've been using a mutable dictionary for this.
Once the invitation has been sent/accepted with a new session, use the MCSession delegate method to update the dictionary:
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
if (state==MCSessionStateConnected){
_myPeerSessions[peerID.displayName] = session;
}
else if (state==MCSessionStateNotConnected){
//This is where the session can be disconnected without
//affecting other peers
[session disconnect];
[_myPeerSessions removeObjectForKey:peerID.displayName];
}
}
All peers can be accessed with a method that returns all values of the dictionary, and in turn all connectedPeers (in this case one) for each MCSession:
- (NSArray *)allConnectedPeers {
return [[_myPeerSessions allValues] valueForKey:#"connectedPeers"];
}
Sending data to a particular peer or via broadcast can be done with a method like this:
- (void)sendData:(NSData *)data toPeerIDs:(NSArray *)remotePeers reliable:(BOOL)reliable error:(NSError *__autoreleasing *)error {
MCSessionSendDataMode mode = (reliable) ? MCSessionSendDataReliable : MCSessionSendDataUnreliable;
for (MCPeerID *peer in remotePeers){
NSError __autoreleasing *currentError = nil;
MCSession *session = _myPeerSessions[peer.displayName];
[session sendData:data toPeers:session.connectedPeers withMode:mode error:currentError];
if (currentError && !error)
*error = *currentError;
}
}

Have you tried disconnecting the session before the application closes? This should remove the peer from the session properly and cleanup any resources allocated for the peer.
Specifically I mean something like [self.peer disconnect] in applicationWillTerminate:

I've been having similar problems. It seems though that if I have run my app on one iOS device, and connected to another, then quit and relaunch (say when I rerun from Xcode), then I am in a situation where I get a Connected message and then a Not Connected message a little later. This was throwing me off. But looking more carefully, I can see that the Not Connected message is actually meant for a different peerId than the one that has connected.
I think the problem here is that most samples I've seen just care about the displayName of the peerID, and neglect the fact that you can get multiple peerIDs for the same device/displayName.
I am now checking the displayName first and then verifying that the peerID is the same, by doing a compare of the pointers.
- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {
MyPlayer *player = _players[peerID.displayName];
if ((state == MCSessionStateNotConnected) &&
(peerID != player.peerID)) {
NSLog(#"remnant connection drop");
return; // note that I don't care if player is nil, since I don't want to
// add a dictionary object for a Not Connecting peer.
}
if (player == nil) {
player = [MyPlayer init];
player.peerID = peerID;
_players[peerID.displayName] = player;
}
player.state = state;
...

I couldn't get the accepted answer to ever work, so what i did instead is have a timer that would fire to reset the connection when the browser would report not connected and there were no other connected peers.
-(void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
//DebugLog(#"session didChangeState: %ld",state);
if(resetTimer != nil){
[resetTimer invalidate];
resetTimer = nil;
}
if(state == MCSessionStateNotConnected){
[session disconnect];
[peerSessions removeObjectForKey:peerID.displayName];
[self removeGuidyPeerWithPeerID:peerID];
//DebugLog(#"removing all guides from peer %#",peerID);
if([localSession connectedPeers].count == 0){
DebugLog(#"nothing found... maybe restart in 3 seconds");
dispatch_async(dispatch_get_main_queue(), ^{
resetTimer = [NSTimer
scheduledTimerWithTimeInterval:3.0
target:self selector:#selector(onResetTimer:)
userInfo:nil
repeats:NO];
}
);
}
}
...
}

You can delete the peer from the MCBrowserViewController with following code in Swift 3:
self.mySession.cancelConnectPeer(self.myPeerID)

Related

Proper design of database fetch when app is launching

Maybe this is very repeated question and I'm sorry if so, but after reading all similar subjects & tutorials and trying by many different ways, I'm always unable to have a proper design. So I tried to be pragmatic and simplify things the most as follow:
My app fetches 3 SQL queries which are pretty quick but they contain all needed information for the app. So I would wait for them before I can refresh UI controls.
I perform db fetch by creating 3 separates NSURLSessionDataTask in a singleton class. Something like:
-(void)fetchdb {
[self runDataTask:request1];
[self runDataTask:request2];
[self runDataTask:request3];
}
-(void)runDataTask: request {
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
}
I call this fetch in viewDidLoad of the initial view controller:
- (void)viewDidLoad {
[super viewDidLoad];
[mySingletonClass fetchDb:self];
// other stuffs
}
When didCompleteWithError from NSURLSessionDelegate of all three tasks is called and all my three lists build by task 1, 2 and 3 are filled, I call refreshAll method of my initial view controller:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error{
if (error == nil){
[self.delegate buildTargetList];
if (list1.count>0 && list2.count>0 && list3.count>0)
[initialViewController refreshAll];
}
}
Finally refreshAll refreshes all UI by a dispatch_sync from main_queue (I have to use sync because in the same block I would first fill Arrays used by UI that I want to refresh):
-(void)refreshAll {
dispatch_sync(dispatch_get_main_queue(),^{
[self buildAllLocalArraysForAllUIByUsingSingletonClassLists];
[self refreshAllUI];
});
}
The result's:
Most of time, it works fine but I have sometimes unexpected behaviours such as:
“Collection <__NSArrayM: 0xb1a2970> was mutated while being enumerated”
NSRangeException
or I discovered that refreshAll might not be called at all or strangely is called twice!
Could anyone help me to understand what am I doing wrong or if my design is totally wrong? I'm very very new to IOS dev. Thanks.

GameKit matchmaking fails for 3G connections

I am making a multiplayer game for iOS and I read the material in Apple Developer Center, specifically this one. Here is my code for custom matchmaking, which is pretty straightforward:
- (void)findProgrammaticMatch {
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
request.defaultNumberOfPlayers = 2;
request.playersToInvite = nil;
request.playerAttributes = 0;
request.playerGroup = 0;
UILabel *loading = (UILabel *)[aiw viewWithTag:792];
[[GKMatchmaker sharedMatchmaker] findMatchForRequest:request withCompletionHandler:^(GKMatch *match, NSError *error) {
if (error){
//error handling
[loaderLayer stopAnimating];
UIButton *cancelButton = (UIButton *)[loaderLayer viewWithTag:442];
[cancelButton setTitle:#"Go Back" forState:UIControlStateNormal];
loading.text = #"Cannot find any players. Please try again later.";
} else if (match != nil) {
//save match
self.match = match;
self.match.delegate = self;
loading.text = #"Found a player. Preparing session...";
if (!self.matchStarted && match.expectedPlayerCount == 0) {
self.matchStarted = YES;
//begin game logic
[self.scene setState:1];
self.myTicket = 1000+arc4random_uniform(999);
[self.scene send:self.myTicket];
[self stopLoading];
}
}
}];
}
However, matchmaking fails when one or more devices are connected to the internet via cellular networks. When I investigated the underlying error I found out that even if it is a wifi to wifi case, the completion handler does not work as intended. That is, match.expectedPlayerCount is never 0. Instead, the game starts when - (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state handler is invoked after the completion handler as following:
...
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
switch (state) {
case GKPlayerStateConnected:
self.matchStarted = YES;
//begin game logic
[self.scene setState:1];
self.myTicket = 1000+arc4random_uniform(999);
[self.scene send:self.myTicket];
[self stopLoading];
break;
...
The problem now is if a device with 3g is connected (and matched-sort of) didChangeState is never invoked. I checked for several other related questions on the internet and this site, although they are far from being satisfactory. I also read that sandbox servers of Game Center are not reliable and for some people production version worked perfectly(it just works!) despite the errors in sandbox mode, but I don't want to take that risk. Has anybody have experienced similar problem with their multiplayer game?
Hgeg,
There is nothing wrong with your code.
You have to allow cellular data usage to your app which needs users permission.
The following paragraph is selected from Apple's support website :
At the Foundation layer, you can use the setAllowsCellularAccess:
method on NSMutableURLRequest to specify whether a request can be sent
over a cellular connection. You can also use the allowsCellularAccess
to check the current value.
At the Core Foundation layer, you can achieve the same thing by
setting the kCFStreamPropertyNoCellular property before opening a
stream obtained from the CFSocketStream or CFHTTPStream APIs.
In older versions of iOS, you can continue to use the
kSCNetworkReachabilityFlagsIsWWAN as a best-effort way of determining
whether traffic will be sent over a cellular connection, but you
should be aware of its limitations.
Good luck
Iman
According to the latest apple news, from iOS 9, the sand box mode will no longer exist, instead of the sandbox you'll have one unified environment.
So you'll have just one unified environments where you can share the same accounts, this should solve all the usual problems from the SandBox mode.
The new Unified System it's also compatible with TestFlight so you'll be able to test you code across multiple device and accounts.
All of these changes will be made directly by apple, so the only think that you can do it's to wait until they update to the new system, so far it's the only way to be sure that it's not a Sand Box problem.
For more info please have a loot at the WWDC video
Based on the code that you have shown us, there should'nt be any issue regardless of the connection type, 3G or otherwise; however, if you previously interspersed code for exception handling that was tied back to connection status, or for graphics which represent a loading state, something could be tied up elsewhere logically and produce this error at this point in the game logic. Even a corrupt spinner graphic can become an issue.
Did you have any other exception handlers in the code that called the following:
request.playersToInvite
or
request.playerGroup
or
that changed a loader layer characteristic?

Disable WiFi from Multi Peer Connectivity

I have gone over the documentation, but there isn't much information on Multipeer Connectivity related to choosing a possible medium for peers to connect.
Multipeer Connectivity automatically discovers peers based on WiFi, or Bluetooth. Is there a way to limit this to only Bluetooth?
As #kdogisthebest correctly states, there's no way to force Multipeer Connectivity to use a particular network technology, but as your question relates to a particular problem with WiFi, this answer details what I'm doing to work around that.
I've worked around the issue of 'phantom' peers over WiFi by sending a shortened timestamp in the discoveryInfo when creating the MCNearybyServiceAdvertiser. There are several caveats here:
1) This solution assumes both devices have the same time. I ensure this by using a modified version of ios-ntp as the app's time source.
2) It also assumes that Advertising and Browsing do not run for too long. I have a set length of 60 seconds for discovery phases, and I completely re-init the browser/advertiser on each restart.
3) MPC doesn't seem to like too many bytes in the discoveryInfo so sending an NSTimeInterval based on epoch doesn't work. I had to truncate them.
So when my app enters discovery mode, it starts browsing and advertising simultaneously. The advertising code looks like:
- (void)startAdvertising {
if (_advertising){
NSLog(#"Already advertising");
return;
}
self.acceptedPeerIDNameMap = [NSMutableDictionary dictionary];
NSInteger timeStamp = [self shortenedNetworkTimeStamp];
NSDictionary *discoveryInfo = #{kAdvertisingDiscoveryInfoTimestampKey:[NSString stringWithFormat:#"%ld",(long)timeStamp]};
NSLog(#"Starting advertiser");
self.serviceAdvertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:_myPeerID
discoveryInfo:discoveryInfo
serviceType:kServiceType];
_serviceAdvertiser.delegate = self;
[_serviceAdvertiser startAdvertisingPeer];
self.advertising = YES;
}
The method shortenedNetworkTimestamp just takes an NSTimeInterval (either using the ntp framework or timeIntervalSinceReferenceDate and removing 1400000000 from it.
Then when the browser discovers a peer, it checks whether the advertiser's timestamp is within the known discovery duration (in my case 60 seconds):
- (void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary *)info {
DLog(#"Browser found peer ID %#",peerID.displayName);
//Only one peer should invite the other
BOOL shouldInvite = [peerID.displayName compare:_myPeerID.displayName]==NSOrderedAscending;
//Don't re-send invitations
if (_peerInfoDisplayNameMap[peerID.displayName]){
DLog(#"Already connected to peerID %#",peerID.displayName);
shouldInvite = NO;
}
else if (_invitedPeerIDNameMap[peerID.displayName]){
DLog(#"Already invited peerID %#",peerID.displayName);
shouldInvite = NO;
}
//Invite if discovery info is valid
if (shouldInvite && [self discoveryInfoIsValid:info]) {
DLog(#"Inviting");
_invitedPeerIDNameMap[peerID.displayName] = peerID;
MCSession *session = [self availableSession];
[_serviceBrowser invitePeer:peerID toSession:session withContext:nil timeout:0];
}
else {
DLog(#"Not inviting");
}
}
The discovery info validity check is pretty simple - just make sure the timestamp sent in the info is inside of the discovery time range (in my case kDiscoveryPhaseDuration is 60 seconds):
- (BOOL)discoveryInfoIsValid:(NSDictionary *)info {
BOOL isValid = YES;
NSString *infoTimeStamp = info[kAdvertisingDiscoveryInfoTimestampKey];
NSTimeInterval sentTimeStamp = (infoTimeStamp) ? [infoTimeStamp doubleValue] : -1;
NSTimeInterval currentTimeStamp = [self shortenedNetworkTimeStamp];
if (sentTimeStamp==-1 || (currentTimeStamp - sentTimeStamp) > kDiscoveryPhaseDuration){
DLog(#"Expired discovery info (current=%f, sent=%f)",currentTimeStamp,sentTimeStamp);
isValid = NO;
}
return isValid;
}
Hopefully this helps. There are many other quirks in MPC that I'm handling in my own code but I think the above covers this specific problem.
This isn't possible with Multipeer Connectivity. There are no methods Apple puts in place to limit the connection to Bluetooth.
One answer here: Multipeer connectivity over Bluetooth? states "There is no explicit setting for bluetooth or Wifi, It will connect devices in whatever possible way they are available."

Message sent to a deallocated instance

Background:
All my OpenTok methods are in one ViewController that gets pushed into view, like a typical Master/detail VC relationship. The detailVC connects you to a different room depending on your selection. When I press the back button to pop the view away, I get a crash (maybe 1 out of 7 times):
[OTMessenger setRumorPingForeground] message sent to deallocated instance xxxxx
or
[OTSession setSessionConnectionStatus:]: message sent to deallocated instance 0x1e1ee440
I put my unpublish/disconnect methods in viewDidDisappear:
-(void)viewDidDisappear:(BOOL)animated{
//dispatch_async(self.opentokQueue, ^{
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
Here is a trace:
Here is the DetailViewController on Github: link here
How to reproduce:
Make a selection from the MasterVC, that takes you into the DetailVC which immediately attempts to connect to a session and publish
Go back to previous, MasterVC quickly, usually before the session has had an a chance to publish a stream
Try this several times and eventually it will crash.
If I slow down and allow the publisher a chance to connect and publish, it is less likely to cause a crash.
Expected result:
It should just disconnect from the session/unpublish and start a new session as I go back and forth between the Master/DetailVC's.
Other:
What is your device and OS version?
iOS 6
What type of connectivity were you on?
wifi
Zombies Enabled?
Yes
ARC Enabled?
Yes
Delegates set to nil?
Yes, as far as I know
Any help solving this crash would be greatly appreciated. Perhaps I'm missing something basic that I just can't see.
What seems to happen is that the OTSession object in the OpenTok library continues to to send messages to objects in that library that have since been deallocated by switching views. The library has a [session disconnect] method that works fine if you give it enough time, but it takes close to 2-3 seconds, and that's a long time to pause an app between views.
This might be a stupid question, but:
Is there anyway to stop all processes initiated by a certain VC?
Closing the session from viewWillDisappear() works if you can determine for sure that the view is going to be popped, not pushed or hidden. Some answers suggest putting this code in dealloc(). Regarding those suggestions, Apple says,
You should try to avoid managing the lifetime of limited resources using dealloc.
So, here is how you can determine for sure that your view will get popped. viewWillDisappear() is called when the view is popped from the stack, or is otherwise pushed somewhere else. This is the easiest way to determine which, and then unpublish/disconnect if it is truly popped. You can test for this with isMovingFromParentViewController. Also, here is where you can remove specific observers.
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated]
// This is true if the view controller is popped
if ([self isMovingFromParentViewController])
{
NSLog(#"View controller was popped");
// Remove observer
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
...
//dispatch_async(self.opentokQueue, ^{
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
//});
[self doCloseRoomId:self.room.roomId position:self.room.position];
}
else
{
NSLog(#"New view controller was pushed");
}
}
Ref: Testing for Specific Kinds of View Transitions
Looks like OpenTok have a bug with usage NSNotificationCenter inside of OTSession and OTMessenger classes. You can see these classes in call-stack are separated with NSNotificationCenter calls:
You can manually unsubscribe your OTSession object when dealloc (hope OpenTok uses defaultCenter):
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self.session];
}
You need to check if this code (dealloc) is really executed. If not - you need to fix problem of UIViewController deallocation. A lot of other answers contains tips how to help UIViewController to be deallocated.
-(void)viewDidDisappear:(BOOL)animated is called whenever the view is hidden, not only when it is popped from the view stack.
So if you push a view over it, viewWillDisappear will be called and your objects deleted.
This is specially problematic if you load these same objects from viewDidLoad: instead of viewDidAppear:.
Perhaps you should put your unpublish/disconnect code in -(void)dealloc.
This is what Apple suggests:
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
But this is only the last resort to remove observers, still often a good habit to always add it to make sure everything is cleand up on dealloc to prevent crashes.
It's still a good idea to remove the observer as soon as the object is no longer ready (or required) to receive notifications.
I most of the time put such a code in the viewWillDisappear, but I guess that doesn't really matter.
I believe the issue is that your session delegate is not set to nil. Just add the following in your viewDidDisappear:
self.session.delegate=nil;
You must call [super viewDidDisappear:animate]; at the beginning. May be it will fix your issue.
And better cleanup your session and subscriber in dealloc method:
- (void) dealloc {
[self.session removeObserver:self forKeyPath:#"connectionCount"];
if(self.subscriber){
[self.subscriber close];
self.subscriber = nil;
}
if (self.publisher) {
[self doUnpublish];
}
if (self.session) {
[self.session disconnect];
self.session = nil;
}
[self doCloseRoomId:self.room.roomId position:self.room.position];
//[super dealloc]; //for non-ARC
}
According to the stack trace you have posted, the notification center reaches out to an OTSession instance that is still alive. Afterwards, this instance provokes a crash calling methods on deallocated objects.
Adding to that the two different deallocated instance messages, we know there are asynchronous events occuring after the death of some objects that trigger the random crash you are having.
As ggfela suggested, you should make sure to nil out the delegates you have connected to the OpenTok framework. I strongly suggest you do that in the dealloc method as we want to make sure that after that point, no one has any dangling references to your object :
- (oneway void)dealloc
{
self.session.delegate = nil;
self.publisher.delegate = nil;
self.subscriber.delegate = nil;
}
Another odd thing in the code is that your handler for sessionDidConnect: creates a new dispatch_queue every time it is being called in order to call doPublish:. This means that you have concurrent threads sharing the SROpenTokVideoHandler instance which makes it prone to race conditions.

How to transfer data between phones over GKSession without using GKPeerPicker

I'm trying to create an app that transfers data between 2+ phones using GKSession. Thing is there are two options:
First: using the GKPeerPicker.. However here I get stuck at the point where I have to implement my own WIFI interface.. apple provides no instructions on how to do that:
- (void)peerPickerController:(GKPeerPickerController *)picker didSelectConnectionType: (GKPeerPickerConnectionType)type {
if (type == GKPeerPickerConnectionTypeOnline) {
picker.delegate = nil;
[picker dismiss];
[picker autorelease];
// Implement your own internet user interface here.
}
}
Second: Skipping GKPeerPicker and doing the whole thing my self, like in this example. However the app dev documentation doesn't provide any instructions on how to send/receive data without using GKPeerPicker.. (nor could I find any example of that on thew web)
I just figured out how to connect devices without the peerpicker. It was a bit of a guessing game because the documentation is pretty unclear and I've looked for so long on the internet for any info about this. I'll try to explain everything here to clear up any questions anyone in the future might have.
From the documentation:
A GKSession object provides the ability to discover and connect to
nearby iOS devices using Bluetooth or Wi-fi.
This was the first step to understand it for me. I thought the GKPeerPickerController was responsible of the advertising and connecting but GKSession actually does all that.
The second thing to understand is that what is referred to as peers are not necessarily connected to you. They can just be nearby waiting to be discovered and connected to. All peers have a state
GKPeerStateAvailable (this is what's useful!)
GKPeerStateUnavailable
GKPeerStateConnected
GKPeerStateDisconnected
GKPeerStateConnecting
So how do we actually connect? Well first we have to create a GKSession object to be able to find peers around us and see when they become available:
// nil will become the device name
GKSession *gkSession = [[GKSession alloc] initWithSessionID:#"something.unique.i.use.my.bundle.name" displayName:nil sessionMode:GKSessionModePeer];
[gkSession setDataReceiveHandler:self withContext:nil];
gkSession.delegate = self;
gkSession.available = YES; // I'm not sure this if this is the default value, this might not be needed
Now we have some delegate calls to respond to. session:didReceiveConnectionRequestFromPeer: and session:peer:didChangeState (you should also handle the calls of GKSessionDelegate for disconnection and failure appropriately)
-(void)session:(GKSession *)session peer:(NSString *)peerID didChangeState:(GKPeerConnectionState)state
{
if(state == GKPeerStateDisconnected)
{
// A peer disconnected
}
else if(state == GKPeerStateConnected)
{
// You can now send messages to the connected peer(s)
int number = 1337;
[session sendDataToAllPeers:[NSData dataWithBytes:&number length:4] withDataMode:GKSendDataReliable error:nil];
}
else if (state == GKPeerStateAvailable)
{
// A device became available, meaning we can connect to it. Lets do it! (or at least try and make a request)
/*
Notice: This will connect to every iphone that's nearby you directly.
You would maybe want to make an interface similar to peerpicker instead
In that case, you should just save this peer in a availablePeers array and
call this method later on. For your UI, the name of the peer can be
retrived with [session displayNameForPeer:peerId]
*/
[session connectToPeer:peerID withTimeout:10];
}
}
The other peer now received a request that he should respond to.
-(void)session:(GKSession *)session didReceiveConnectionRequestFromPeer:(NSString *)peerID
{
// We can now decide to deny or accept
bool shouldAccept = YES;
if(shouldAccept)
{
[session acceptConnectionFromPeer:peerID error:nil];
}
else
{
[session denyConnectionFromPeer:peerID];
}
}
Finally to receive our little 1337 message
-(void)receiveData:(NSData *)data fromPeer:(NSString *)peer inSession:(GKSession*)session context:(void *)context
{
int number = 1337;
if([data isEqualToData:[NSData dataWithBytes:&number length:4]])
{
NSLog(#"Yey!");
}
}

Resources