EXC_BAD_ACCESS while sending data with game center - ios

I'm trying to make a multiplayer memory game with game center.
I followed the tutorial in this link, connected two devices to a match, got the match started.
In the game, when one device flip a card, the other must flip that card too.To do this, I have to send an id type value to the other device, so I can flip the card in two devices.
Here is the code;
- (void)gameTouchBegin:(id)target {
[target flipTheCard];
[self sendCardMove:target];
}
- (void)sendCardMove:(id)target {
MessageCard message;
message.message.messageType = kMessageTypeCard;
message.target = target;
NSData* data = [NSData dataWithBytes:&message length:sizeof(MessageCard)];
[self sendData:data];
}
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
if (message->messageType == kMessageTypeCard) {
NSLog(#"Other player flipped the card.");
// I can successfully write this line.
// So I can reach inside this function.
MessageCard * messageInit = (MessageCard *) [data bytes];
id gTarget = (id)messageInit->target;
[gTarget flipTheCard];
// At the three code lines above, I got an error.
}
}
As I commented at the end of the code, I got EXC_BAD_ACCESS error and the app crashes.
I know this is a complicated stuff, anyone have an idea?
Thanks in advance.

I think that may cause the problem.
I define the message body like this;
typedef struct {
Message message;
id target;
} MessageCard;
I suppose xCode doesn't let me define "id" type variables in struct. So I can't send id type to other device. There is no solution about that, I must find another way to find out which card it is.
Thank you.

Related

iMessageExt app Error starting application

I created my iMessage extension, when I try to open it, the first screen appears but it is totally frozen, and it does not react in any way.
I've put logs in the viewDidLoad of that first view and nothing appears there, after a few seconds I can already see those logs.
To make the application freezing lose that status, user has to slide screen left or right and back again.
I've tried looking all over the web for someone who happens to be the same, but I could not find anything.
It does not come to mind more screenshots or portions of code add, if you think I should provide some additional information, just let me know
Any help would be appreciated.
Thank you.
UPDATE:
This is my Project Structure.
This is my viewDidLoad code.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"here viewDidLoad iMessage ext~~~!");
[self applyCornerRadiusToBtn];
[self registerPresentationAction];
NSDictionary *user = [self getUserInfoFromHostApp];
if (user) {
NSLog(#"Here != null user info");
//It is assumed that when you enter this point and run this log, the app should navigate to the next screen, but it does not.
[self performSegueWithIdentifier:#"goToYoutubeListIm" sender:nil];
} else {
NSLog(#"Here userInfo null");
}
}
- (NSDictionary *)getUserInfoFromHostApp
{
NSUserDefaults *myDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"group.com.xxxxx"];
NSDictionary *userNameSaved = [myDefaults objectForKey:#"userInfoExt"];;
NSLog(#"userNameSaved in xxxx Ext ==> %#",userNameSaved);
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.xxxx"];
NSLog(#"groupURL ==> %#",groupURL);
return userNameSaved;
}
For all concerned I have found the problem or problems to be accurate.
1) I was creating my controllers type MSMessagesAppViewController. Apparently there should only be one controller of this type.
2) I had logic in the viewDidAppear in my MSMessagesAppViewController. For some strange reason this also caused the problem, I had to get the logic out there and force the user to interact with a button to execute the logic that was in the didAppear

iOS - Wireless Communication Lagging

So I'm making a game that involves wireless communication between multiple iPhones, with one being the host. I am attempting to do so via the MultipeerConnectivity framework, and I've made a MCManager class (an instance of which I put into appDelegate so it's available throughout the app) to handle sending data from one system to another. This is how sending is implemented in my code:
- (void) sendState: (NSString*) str;
//used by the host to send commands to the other connected systems
{
if(appDelegate.mcManager.connected && iAmHost){
NSData *dataToSend = [str dataUsingEncoding: NSUTF8StringEncoding];
NSArray *allPeers = appDelegate.mcManager.session.connectedPeers;
NSError *error;
[appDelegate.mcManager.session sendData:dataToSend
toPeers:allPeers
withMode:MCSessionSendDataReliable
error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
}
}
}
and when the subordinate systems receive the data, MCManager sends the Notification Center a notification and my class, which is looking for that particular notification, grabs it and executes this:
-(void)didReceiveDataWithNotification:(NSNotification *)notification{
if(!iAmHost){
NSData *receivedData = [[notification userInfo] objectForKey:#"data"];
NSString *action = [[NSString alloc] initWithData: receivedData encoding:NSUTF8StringEncoding];
NSLog(#"Recieved:");
NSLog(action); //for debugging purposes, and figuring out timing
//decide how to act depending on the string given
if([action containsString:#"ChangeMaxScore"]){
//the string was formatted as, for example, "ChangeMaxScore105"
NSString* valueStr = [action substringFromIndex:14];
maxScore = (int)[valueStr integerValue];
[self changeMaxScore]; //this method changes the label text that shows the user the value of maxScore
}
else if([action containsString:#"ChangePlayerNo"]){
//strings are formatted as "ChangePlayerNo2" for the second segment in a segmented control with the segments "2", "3", "4"
//so it would be referring to four players
NSString *valueStr = [action substringFromIndex:14];
[playerNumberSegmentedControl setSelectedSegmentIndex: [valueStr integerValue]];
playerNumber = playerNumberSegmentedControl.selectedSegmentIndex + 2;
[self changePlayerNumber];
//Players can either be human or a type of AI (AI-1,AI-2,etc.)
//the segmented control where you choose this is invisible unless that player number is playing
//so this method sets that segmented control visible and interactable (by adding it to the view)
//and removes those segmented controls not in use from the view
}
else if([action containsString:#"ChangeP0State"]){
//changes the value of the first player's segmented control (Human, AI-1, AI-2, etc.
NSString* valueStr = [action substringFromIndex:13];
AIControl0.selectedSegmentIndex = (int)[valueStr integerValue];
}
...
else if([action containsString:#"StartGame"])
[self newGame];
//this method starts the game and, in the process, pushes another view controller
}
}
My issue is that these actions, on the receiving end, are very laggy. For changing the number of players, for instance, the receiver NSLogs "Received: ChangePlayerNo1", and the segmented control on-screen changes its selected segment to the second one, but the stuff that's supposed to show up at that point...doesn't. And when I send the "StartGame" command, the receiver NSLogs that it has received it, I have to wait thirty seconds for it to actually start the game like it was asked.
This delay makes it very hard to test whether my wireless methods are working or not (it works on the host's side, mostly because the host is changing them manually, not responsively - also, all of this works on the other side of my program, which is just the game without wireless support, with several players/AIs on a single screen) and, if not fixed, will definitely prevent the app from being used easily.
I'm curious what causes this and what I can do to fix it. Thank you!

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?

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!");
}
}

Game Center Sandbox : "Could not create game" issue

I'm currently developing a turn based game using Game Center to handle the online functionalities (for matchmaking and turns handling).
I'm using two sandbox accounts - one on my 3gs and one on the ios Simulator.
I've been testing my app using the GKTurnBasedMatchMakerViewController to do the match making for a while without any problems, but I'm now stuck with an issue:
Every time I want to invite another player for a new (with either one or the other player), the GKTurnBasedMatchMakerViewController displays a UIAlertView stating :
Could not create game - Please remove an existing game and try again.
The thing is, I've deleted all the matches for each player (none of them has any game in his list (not even a closed game). So none of the user is in any match at the moment.
In my GKTurnBaseMatchMakerViewControllerDelegate the turnBasedMatchmakerViewController:didFailWithError: is not called.
The only called function called in the delegate- when I click the OK button on the UIAlertView - is turnBasedMatchmakerViewControllerWasCancelled:
The only thing I can think of is that my games are actually not removed from GameCenter, but as I'm removing them using the GKMatchMakerViewController UI, I barely think so.
When quitting from a turn-based match I've implemented the turnBasedMatchmakerViewController:playerQuitForMatch: like this:
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController playerQuitForMatch:(GKTurnBasedMatch *)match
{
if ( [self isLocalPlayerCurrentPlayerForMatch:match] ) {
NSData* endData = match.matchData;
for (GKTurnBasedParticipant* participant in match.participants) {
participant.matchOutcome = GKTurnBasedMatchOutcomeWon;
}
match.currentParticipant.matchOutcome = GKTurnBasedMatchOutcomeLost;
[match endMatchInTurnWithMatchData:endData
completionHandler:^(NSError *error) {
if (error) {
NSLog(#"%#",error.description);
}
}];
}
}
(NB: I only have two players in the game)
where isLocalPlayerCurrentPlayerForMatch is:
- (BOOL) isLocalPlayerCurrentPlayerForMatch:(GKTurnBasedMatch*)match
{
return [[[GKLocalPlayer localPlayer] playerID] isEqualToString:match.currentParticipant.playerID];
}
Has anyone encountered and found a solution to this issue?
Am I doing something wrong here, or is it so obvious I just can't see it?
Thank you very much for any comments that would help me find the root of that issue.
Update
Thanks to #kaan-dedeoglu I managed to know that both users had an empty list of matches (consistent with the displayed state).
I also created a third Sandbox account.
Naming the two first accounts A and B, C the third one.
State 1:
A and B are not linked to any match.
A and B are both getting the "Could not create game" error while creating any game (A invites B, A||B invites other player, A||B creates new automatch).
State 2:
C (working account) can invite B and normally plays a party with B.
C (working) can invite B for another simultaneous party
C (working) invites A to play.
A can't play (can't access the list of current matches, the GKTurnBasedMatchMakerViewController directly goes to the creation of a new game).
C is not working anymore.
A, B and C are now stuck in "Could not create game" error.
As a complement here is how I initialize my GKTurnBasedMatchMakerViewController, but I don't see that being wrong.
- (void) displayMatchMakerVC
{
if (! [[GKLocalPlayer localPlayer] isAuthenticated] ) return;
GKMatchRequest* request = [[[GKMatchRequest alloc] init] autorelease];
int nbPlayers = 2;
request.minPlayers = nbPlayers;
request.maxPlayers = nbPlayers;
GKTurnBasedMatchmakerViewController* matchMakerVC = [[[GKTurnBasedMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
matchMakerVC.turnBasedMatchmakerDelegate = self;
matchMakerVC.showExistingMatches = YES;
[[CCDirector sharedDirector] presentModalViewController:matchMakerVC animated:YES];
}
NB: I'm not using ARC, could that be related to a memory issue? I'm not really a memory management guru, but it seems correct to my understanding.
Any idea of how this could be related to my code and not to game center?
Thank you very much for any answer that could help me go further.
Update 2: turnbasedMatchmakerViewController:didFindMatchMethod:
Here's my turnbasedMatchmakerViewController:didFindMatchMethod: method.
- (void)turnBasedMatchmakerViewController:(GKTurnBasedMatchmakerViewController *)viewController didFindMatch:(GKTurnBasedMatch *)match
{
BLTheme* theme = [[[BLGameConfig sharedConfig] localPlayer] userTheme];
GameSceneRemoteGCLoader* loader = [[GameSceneRemoteGCLoader alloc] initWithGKMatch:match andTheme:theme];
[viewController dismissViewControllerAnimated:NO completion:^{}];
[[CCDirector sharedDirector] replaceScene:loader];
}
When I'm launching an automatch it's launching the exact same error "Could not create game - Please remove an existing game and try again.".
This may or may not be the solution to your problem, but I had a similar issue and solved it in the following way.
It seems that either by default, or somehow, Game Center treats apps with differing CFBundleVersion (build number, not version number, or CFBundleShortVersionString) values as incompatible with one another, and thus does not show matches between apps with incremented build numbers. (Often, developers increment this number as new ad hoc builds or stable releases are distributed during development, so this is quite unfortunate).
To find and remove the "missing" games, I decremented my CFBundleVersion value (which revealed the games), and then deleted the offending matches.
Alternatively, tweaking some settings in iTunes Connect seems to have removed this CFBundleVersion incompatibility. It takes a while to propagate, but I think what did it was tapping on my app, tapping on View Details, making sure the Game Center switch is set to "Enabled", and making sure there is an item in the "Multiplayer Compatibility" table. You could also play with the possibilities within the "Manage Game Center" button from the original app screen, but I think the "Multiplayer Compatibility" setting is what finally allowed me to see all the "old" matches that were previously hidden.
Good luck!
Just to make sure: In both these devices, add these lines in your authentication completion handler and run it once. (then you can comment it out).
[GKTurnBasedMatch loadMatchesWithCompletionHandler:(^)(NSArray *matches, NSError *error) {
for (GKTurnbasedMatch *match in matches) {
[match removeWithCompletionHandler:NULL];
}
}];
This will ensure that all games are removed from your playerID.
It's ridiculous . You don't have to remove an existing match to create a new match. I'm developing a game like this and it actually works.
The following worked for me. First I ran the app on the device for each player, calling quitAllMatches. Then I ran the app again on each device, calling removeAllMatches.
In the long run, it has to be better to clean them up as you go along. But this solved the immediate problem.
-(void) quitAllMatches {
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray* matches, NSError* error) {
for (GKTurnBasedMatch* match in matches) {
GKTurnBasedParticipant* participant = match.currentParticipant;
NSString* playerID = participant.playerID;
NSString* localPlayerID = [GKLocalPlayer localPlayer].playerID;
if ([playerID isEqualToString: localPlayerID]) {
NSArray* participants = match.participants;
for (GKTurnBasedParticipant* participant in participants) {
participant.matchOutcome = GKTurnBasedMatchOutcomeTied;
}
NSData* data = [NSData data];
[match endMatchInTurnWithMatchData: data completionHandler:^(NSError* error) {
if (error) {
WJLog(#"did not end -- error %#", [error localizedDescription]);
}
else {
WJLog(#"match ended!");
}
}];
}
}
}];
}
-(void) removeAllMatches {
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray* matches, NSError* error) {
for (GKTurnBasedMatch* match in matches) {
[match removeWithCompletionHandler:^(NSError* error) {
if (error) {
WJLog(#"error: %#", [error localizedDescription]);
}
else {
WJLog(#"removed match");
}
}];
}
}];
}

Resources