1

I'm trying to incorporate GKGameSession into my Game Center game. I've tried several combinations of the following code: running the commands asynchronously, chaining them in the completion handlers, etc. Every time I see the same result: I can use saveData just fine until I've called getShareURLWithCompletionHandler. After that, any attempt to saveData throws an error.

Here's the simplest version of code that exhibits the problem:

CKContainer *defaultContainer = [CKContainer defaultContainer];
[GKGameSession createSessionInContainer:defaultContainer.containerIdentifier
                              withTitle:@"temp title"
                    maxConnectedPlayers:4
                      completionHandler:^(GKGameSession * _Nullable session, NSError * _Nullable error)
{
    if (error)
    {
        [self printError:error];
    }

    [session getShareURLWithCompletionHandler:^(NSURL * _Nullable url, NSError * _Nullable error)
    {
        if (error)
        {
            [self printError:error];
        }
    }];


    NSData *newData = [NSData dataWithBytesNoCopy:@"abcdefghijklmnopqrstuvwxyz" length:26];
    [reSession saveData:newData completionHandler:^(NSData * _Nullable conflictingData, NSError * _Nullable error)
    {
            if (error)
            {
                [self printError:error];
            }
    }];


}];

In most cases, the saveData call simply crashes:

malloc: *** error for object 0x32df14: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug

But sometimes it throws an error:

GKGameSessionErrorDomain:GKGameSessionErrorUnknown

I've tried different kinds of data being saved. I've tried making the calls sequential by chaining all the calls in completion handlers. I've tried doing the URL fetch and data save inside and outside of the creationSession completion handler.

Is there something I'm doing wrong here?

Thunk
  • 4,099
  • 7
  • 28
  • 47

2 Answers2

1

I see the same, but with a more useful error:

The requested operation could not be completed because the session has been updated on the server, causing a conflict.

The save documentation says,

It is up to the developer to decide how to handle save conflicts.

Here though, retrying the save fails every time, forever. So yeah, that's the same state you're in.

However, when the player joining the game enters the URL on their device, their GKGameSessionEventListener's didAddPlayer: is called, and then if they save... they get the same conflict error, but if they then retry the save... it works!

The player creating the link is locked out of saving or updating game state, until joining players have updated the data. When the other player saves, the original player gets a call to session:player:didSave: on the GKGameSessionEventListener.

At that point the original player can then save as expected.

Graham Perks
  • 23,007
  • 8
  • 61
  • 83
  • Very interesting find! I opened bug with Apple back in October, and after several weeks and no meaningful response, I ripped out all of the GK* code and rolled my own solution. This actually re-enforces a different bug I opened: there's no way for an invitee to decline an invite. Apple closed that bug "by design" saying "ignoring the invite is declining." But your finding shows that the originator will be stuck in an unplayable game if the invitee never acknowledges the invite and saves the data. – Thunk Feb 01 '17 at 05:00
  • 1
    Cool! I do wish Apple had a gaming SDK evangelist, or if they do, a more public-facing one. Can you share more info on your own custom solution? Such as what you used server-side? – Graham Perks Feb 01 '17 at 13:00
  • 1
    I replicated almost all functionality of GKturnbasedmatch and GKgamesession using just Apple's own CloudKit. After watching the wwdc videos on CloudKit, I am convinced that these GK* items are built on top of it. By having my own control of the record schema, I have more precise control of notifications. Player search, automatch, turn management, cloud storage all achieved with CloudKit. I have not tried using CKShare for share urls yet, but it seems to be the same thing. Since comments are so limited, If you'd like more info, maybe open a new question and I can answer in detail. – Thunk Feb 01 '17 at 20:34
  • 1
    P.s. i have not attempted to replicate real-time streams using just CloudKit. I think that will require something else. – Thunk Feb 01 '17 at 20:34
0

You should put one block inside other. Because blocks may be completed in any order.

I have working code like this:

NSData *newData = [NSData dataWithBytesNoCopy:@"abcdefghijklmnopqrstuvwxyz" length:26];
[reSession saveData:newData completionHandler:^(NSData * _Nullable conflictingData, NSError * _Nullable error)
{
        if (error)
        {
            [self printError:error];
        } else {
[session getShareURLWithCompletionHandler:^(NSURL * _Nullable url, NSError * _Nullable error)
{
    if (error)
    {
        [self printError:error];
    }
}];

} }];

Andrey Tozik
  • 115
  • 7
  • thanks, but it's not a block-execution thing. This originally popped up when the save happened 10-15 minutes after getting the URL. The point is, regardless of whether the blocks are nested or not, or how much time elapses, every attempt to save throws an error if it occurs anytime after getting the URL. I'd be curious to see what happens in your code if you swap the save and getShare calls (nest the save inside the getShare completion block) – Thunk Dec 06 '16 at 17:24