Google Play Games SDK for iOS giving strange errors when using Snapshots - ios

I'm trying to use Google's Play Games SDK for iOS. Upon signing my user in and then running one of their straight-forward methods:
[GPGSnapshotMetadata
openWithFileName:saveStoreName
conflictPolicy:GPGSnapshotConflictPolicyRemoteWins
completionHandler:^(GPGSnapshotMetadata *snapshot,
NSString *conflictId,
GPGSnapshotMetadata *conflictingSnapshotBase,
GPGSnapshotMetadata *conflictingSnapshotRemote,
NSError *error) {
NSLog(#"We got here. It must be an actual miracle.");
}];
instead of ever being sent to the completion handler, I get this error:
2014-07-13 22:01:33.700 MegaBits[6931:f007] WARNING: Returning empty data: id not found
2014-07-13 22:01:33.707 MegaBits[6931:f007] VERBOSE: Snapshot cache fully expired: refreshing all.
2014-07-13 22:01:33.892 MegaBits[6931:8d07] VERBOSE: Request had Apiary Status Code: 7
2014-07-13 22:01:33.894 MegaBits[6931:8d07] VERBOSE: Parsed error<http_code: 403, domain: usageLimits, reason: accessNotConfigured>
And those last 2 lines repeat a number of times.
I'm not sure what any of this means. It doesn't appear to be documented anyway, and so far as I can tell, Apiary is an API-generating startup. This error only comes from running this method. Commenting it out results in my code continuing to run smoothly. Apiary also isn't a service I'm using anywhere else.
We also haven't even approached 1% of our quota, so we're not being rate limited.

Related

Firebase A/B test not counting users when activation event is used on iOS

We're using the current version of the Firebase iOS framework (5.9.0) and we're seeing a strange problem when trying to run A/B test experiments that have an activation event.
Since we want to run experiments on first launch, we have a custom splash screen on app start that we display while the remote config is being fetched. After the fetch completes, we immediately activate the fetched config and then check to see if we received info about experiment participation to reconfigure the next UI appropriately. There are additional checks done before we determine that the current instance, in fact, should be part of the test, thus the activation event. Basically, the code looks like:
<code that shows splash>
…
[[FIRRemoteConfig remoteConfig] fetchWithExpirationDuration:7 completionHandler:^(FIRRemoteConfigFetchStatus status, NSError * _Nullable error) {
[[FIRRemoteConfig remoteConfig] activateFetched];
if (<checks that see if we received info about being selected to participate in the experiment and if local conditions are met for experiment participation>) {
[FIRAnalytics logEventWithName:#"RegistrationEntryExperimentActivation" parameters:nil];
<dismiss splash screen and show next UI screen based on experiment variation received in remote config>
} else {
<dismiss splash screen and show next UI screen>
}
}
With the approach above (which is completely straight-forward IMO) does not work correctly. After spending time with the debugger and Firebase logging enabled I can see in the log that there is a race-condition problem occurring. Basically, the Firebase activateFetched() call does not set up a "conditional user property experiment ID" synchronously inside the activateFetched call but instead sets it up some short time afterward. Because of this, our firing of the activation event immediately after activateFetched does not trigger this conditional user property and subsequent experiment funnel/goal events are not properly marked as part of an experiment (the experiment is not even activated in the first place).
If we change the code to delay the sending of the activation event by some arbitrary delay:
<code that shows splash>
…
[[FIRRemoteConfig remoteConfig] fetchWithExpirationDuration:7 completionHandler:^(FIRRemoteConfigFetchStatus status, NSError * _Nullable error) {
[[FIRRemoteConfig remoteConfig] activateFetched];
if (<checks that see if we received info about being selected to participate in the experiment and if local conditions are met for experiment participation>) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[FIRAnalytics logEventWithName:#"RegistrationEntryExperimentActivation" parameters:nil];
<dismiss splash screen and show next UI screen based on experiment variation received in remote config>
}
} else {
<dismiss splash screen and show next UI screen>
}
}
the conditional user property for the experiment gets correctly setup beforehand and triggered by the event (causing experiment activation and subsequent events being correctly marked as part of the experiment).
Now, this code obviously is quite ugly and prone to possible race-conditions. The delay of 0.5 seconds is conservatively set to hopefully be enough on all iOS devices but ¯_(ツ)_/¯. I've read the available documentation multiple times and tried looking at all available API methods with no success in figuring out what the correct point of starting to send events should be. If the activateFetched method uses an asynchronous process of reconfiguring internal objects, one would expect a callback method that indicates to the caller the point in time when everything is done reconfiguring and ready for further use by the application. Seems the framework engineers didn't anticipate a use-case when someone needs to send the activation event immediatly after remote config profile activation…
Has anyone else experienced this problem? Are we missing something in the API? Is there a smarter way of letting activateFetched finish its thing?
Hope some Firebase engineers can chime-in with their wisdom as well :)
Thanks

WatchKit extension crash: "Program ended with exit code: 0"

For people wanting to reply quickly without reading the post: I am not hitting any memory limits. Read the whole post for details.
My WatchKit extension cannot properly function without the user first being "onboarded" through the phone app. Onboarding is where the user must accept the permissions that we require, so it's very crucial.
On my WatchKit extension, I wanted to display a simple warning for users who had not finished onboarding within our phone app yet.
As such, I thought I'd get the status of onboarding from the phone in two ways:
When the user opens the app/the app is activated (I use the willActivate method to detect this)
When the app finishes onboarding it sends a message to the watch of its completion (if the extension is reachable, of course)
Both of these combined would ensure that the status of onboarding is always kept in sync with the watch.
I wrote the first possibility in, utilizing reply handlers to exchange the information. It worked just fine, without any troubles. The warning telling the user to complete disappears, the extension does not crash, and all is well.
I then wrote in the second possibility, of the extension being reachable when the user finishes onboarding (with the phone then directly sending the companion the new status of onboarding). My extension crashes when it receives this message, and I am stuck with this odd error.
Program ended with exit code: 0
My extension does not even get a chance to handle the new onboarding status, the extension just quits and the above error is given to me.
I am not hitting any sort of memory limit. I have read the technical Q&A which describes what a memory usage limit error looks like, and I don't receive any sort of output like that whatsoever. As well, before the extension should receive the message, this is what my memory consumption looks like.
I have monitored the memory consumption of the extension right after finishing onboarding, and I see not a single spike indicating that I've gone over any kind of threshold.
I have tried going line by line over the code which manages the onboarding error, and I cannot find a single reason that it would crash with this error. Especially since the reply handler method of fetching the onboarding status works so reliably.
Here is the code of how I'm sending the message to the watch.
- (void)sendOnboardingStatusToWatch {
if(self.connected){
[self.session sendMessage:#{
LMAppleWatchCommunicationKey: LMAppleWatchCommunicationKeyOnboardingComplete,
LMAppleWatchCommunicationKeyOnboardingComplete: #(LMMusicPlayer.onboardingComplete)
}
replyHandler:nil
errorHandler:^(NSError * _Nonnull error) {
NSLog(#"Error sending onboarding status: %#", error);
}];
}
}
(All LMAppleWatchCommunicationKeys are simply #define'd keys with exactly their key as the string value. ie. #define LMAppleWatchCommunicationKey #"LMAppleWatchCommunicationKey")
Even though it's never called by the extension, here is the exact receiving code of the extension which handles the incoming data, if it helps.
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *, id> *)message {
NSString *key = [message objectForKey:LMAppleWatchCommunicationKey];
if([key isEqualToString:LMAppleWatchCommunicationKeyOnboardingComplete]){
BOOL newOnboardingStatus = [message objectForKey:LMAppleWatchCommunicationKeyOnboardingComplete];
[[NSUserDefaults standardUserDefaults] setBool:newOnboardingStatus
forKey:LMAppleWatchCommunicationKeyOnboardingComplete];
dispatch_async(dispatch_get_main_queue(), ^{
for(id<LMWCompanionBridgeDelegate> delegate in self.delegates){
if([delegate respondsToSelector:#selector(onboardingCompleteStatusChanged:)]){
[delegate onboardingCompleteStatusChanged:newOnboardingStatus];
}
}
});
}
}
Before including this onboarding-related code, my WatchKit extension was tested by over 100 people, without any troubles. I am using the exact same custom error dialogue that I was using before, just with a different string. I cannot for the life of me figure out what is causing this crash, and the ambiguity of it has given me very little to work with.
Any help would be greatly appreciated. Thank you very much for taking your time to read my post.
Edit: I just tried creating a symbolic breakpoint for exit(), which is never hit. If I call exit() myself, it calls the breakpoint, so I know the breakpoint itself is working.

Getting "malloc: *** error: incorrect checksum for freed object" inconsistently

The full error is:
app(85540,0x38661a8) malloc: *** error for object 0x11214f84:
incorrect checksum for freed object - object was probably modified after being freed.
So I'm getting something that is quite hard to replicate and I suspect is has something to do with the way I have my block set up. What I'm trying to get is the current online status of gamers on Xbox Live, so I allocate 2 NSMutuableDictionaries in viewDidLoad. onlinePlayers is for holding the online status values of the gamers so it's not checked over and over again in cellForRowAtIndexPath when scrolling up and down. checkedPlayers is to prevent multiple calls going out trying to get the status of the same player. Anyway, if I keep launching the simulator over and over again, it will be fine 29/30 launches, but it always crashes at least once on launch with the above error when I'm trying to set the online status value for a gamer:
NSString* gamertag = cell.gamerTagLabel.text;
if (![_checkedPlayers containsObject:gamertag]) {
[_checkedPlayers addObject:gamertag];
[Utilities processJSONDataWithGamertag:gamertag andBlock:^(NSData *jsonData) {
id onlineStatus;
NSDictionary *allXboxAttributes = [Utilities returnJSONObject:jsonData];
// Get current Xbox Live Data
if ([allXboxAttributes objectForKey:#"data"]) {
NSDictionary *dataXboxAttributes = [allXboxAttributes objectForKey:#"data"];
onlineStatus = [dataXboxAttributes objectForKey:#"online"];
// Crashes on the line below
[_onlinePlayers setObject:onlineStatus forKey:gamertag];
// Return to main thread and update online status
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}];
}
If it helps for some reason, the value being returned from dataXboxAttributes is a BOOL. Any help would be much appreciated. Thanks!
You are getting the error in that method but the problem might be elsewhere.
To find the source of the problem, in XCode go to Product > Scheme > Edit Scheme, and under Diagnostics tab enable all the Malloc settings and Guard Malloc.
With that, run your application again, and XCode will stop at the line causing the problem.
from your code it looks like the completion block of processJSON isnt always on the main thread, synchronize access to onlinePlayers.
#synchronized will do I guess

Removing a GKTurnBasedMatch which is in an invalid state

I am doing some experimentation to try to learn about GameKit and I made a simple game and an interface which lists my player's matches. I am trying to add the ability to remove games using the removeWithCompletionHandler: method on the match, but I am having trouble removing a GKTurnBasedMatch which seems to have entered an invalid state.
A po of the match in question prints:
$0 = 0x1d590d20 <GKTurnBasedMatch 0x1d590d20 id:858d8257-cc49-4060-b1d8-38c09a929e3c status:Ended message: taken:2013-03-08 18:08:47 +0000 created:2013-03-08 03:24:14 +0000
current:<GKTurnBasedParticipant 0x1d58c020 - id:G:1717956303 (local player) status:Invited outcome:None lastTurn:(null)>
participants:
<GKTurnBasedParticipant 0x1d58bc90 - id:G:1717239488 status:Done outcome:Quit lastTurn:2013-03-08 18:08:47 +0000>
<GKTurnBasedParticipant 0x1d58c020 - id:G:1717956303 (local player) status:Invited outcome:None lastTurn:(null)>
>
Which seems to indicate that the match has been ended. However, one of the participants has the outcome:None, which I am led by the docs to believe is invalid for an ended game. Trying to simply remove the game gives:
The requested operations could not be completed because one or more parameters are invalid.
While trying to set the outcomes and end the game gives:
The requested operation could not be completed because the session is in an invalid state.
I thought perhaps I could not remove the game because the local player is the active participant, but both participantQuitInTurnWithOutcome:... and endTurnWithNextParticipants:... both give the error:
The requested operation could not be completed because the session is in an invalid state.
as well. Am I doing something wrong or did I somehow create an unremovable game?
P.S. I am also unable to remove the games through the Game Center provided interface, where they are listed under the "Game Over" section.
This is how I managed to remove all invalid matches.
I checked the status of the current participant, if it's invited, I called declineInviteWithCompletionHandler, otherwise I called participantQuitInTurnWithOutcome.
In both completion blocks, I then called removeWithCompletionHandler to remove the match.
This generated a few errors but the matches were still removed so my list is clean.
And here is a workaround on how to avoid getting to this state to begin with. This has the added benefit that the invitee never even get a notification if the inviter quits before finishing his/hers first turn.
In playerQuitForMatch, first end the turn and then in the completion handler, immediately quit the match. Like so,
[match endTurnWithNextParticipants:[NSArray arrayWithObject:nextParticipant]
turnTimeout:GKTurnTimeoutDefault
matchData:nil completionHandler:^(NSError *error) {
if (error) {
NSLog(#"%#", error);
}
[match participantQuitOutOfTurnWithOutcome:GKTurnBasedMatchOutcomeQuit
withCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"%#" ,error);
}
}];
}];
Unfortunately, I've run into the exact same error. To help others understand the problem, in hopes of researching a solution, you can recreate this by inviting a friend to a match, but then quitting the match during the first turn before you ever submit that turn to the invited player. Then, the hosting player removes the match from Game Center. On the invited player's device, they will have a match that looks like the one referenced above, that cannot be deleted. I've tried all types of workaround solutions.
I haven't had any luck yet, but I will update my answer with a solution if I find one. I'm currently trying to ship a Game Center game and so I have to find some way around this. I'll have a conclusion within the next day or two.
UPDATE:
I went over my non-deleteable matches, and they are almost the same as yours except my player with the Invited status also has the match outcome of Won. It seems the key to put the match into an invalid state is to have one player status be Invited instead of Done, but have the match status be Ended. That's the common element between our two cases, and it's an edge case in Apple's bizantine Game Center code. It wouldn't surprise me if they simply screwed up this edge case and expected you to "just know" that you weren't supposed to put the players in that state (undoubtedly if you read their docs closely enough you'll be able to piece this together eventually).
My conclusion is that Apple has an edge case on their hands that goes unnoticed easily because it only can happen if you quit a new match, and only if you invite a friend, two cases you might not test often. Also, if you aren't setting match outcomes improperly I'm guessing it will never happen so they just never caught it.
Since I haven't shipped yet, I'm going to configure my app to detect matches in this state and ignore them. I'm going to report to the console a count of how many matches are in this state, just so I can ensure its not growing past this point. Then, I'm going to analyze my quitting code and make sure that in this edge case that I simply can never put a match into this state again. I think given what we know we've got to be proactive, and just suck up the fact that some matches slipped through.
Hopefully you haven't shipped yet, so the error case matches will be localized to the Game Center Sandbox environment. Actually, the bad matches are just localized to your test user in the Sandbox, so you could just throw away that user and start with a new user once you've corrected your issue. If you implement my above suggestions you should be able to confirm your app is working properly before taking this step.
If anyone can actually find a way to remove these error matches once they exist, please let us know, but I hope my suggestions and identification of the actual cause will be enough to help you proceed with your project.
I found the solution. For invalid matches just use -participantQuitOutOfTurn method and then -removeWithCompletionHandler method. It will be completely removed.
I have a similar situation, if slightly different. I start a match, invite a second sandbox account to play, then the first player quits and deletes the game before the second player can respond. When the second player tries to then quit the game, they get this error:
Error quitting match in turn: Error Domain=GKErrorDomain Code=22 "The requested operation could not be completed because the specified participant is invalid."
UserInfo=0x1f5de800 {
GKServerStatusCode=5097,
NSUnderlyingError=0x1f58b610 "The operation couldn’t be completed.
status = 5097,
Invalid state: turn sent to playerId:1952436619 in slotIndex: 0 for sessionId: 698b074b-fa0b-4505-834f-1b4305b7eecb : expected slot state: Active but found: Inactive",
NSLocalizedDescription=The requested operation could not be completed because the specified participant is invalid.
}
So as far as I can tell, that's happening because the next participant to move already has a status of "Done," which I'm guessing is because they already quit:
<GKTurnBasedMatch 0x1f532b20 id:698b074b-fa0b-4505-834f-1b4305b7eecb status:Open message: taken:2013-03-30 19:53:47 +0000 created:2013-03-30 18:29:09 +0000
current:<GKTurnBasedParticipant 0x1f532b80 - id:G:1952433332 (local player) status:Active outcome:None lastTurn:2013-03-30 19:53:47 +0000>
participants:
<GKTurnBasedParticipant 0x1f532b70 - id:G:1952436619 status:Done outcome:Lost lastTurn:2013-03-30 18:29:10 +0000>
<GKTurnBasedParticipant 0x1f532b80 - id:G:1952433332 (local player) status:Active outcome:None lastTurn:2013-03-30 19:53:47 +0000>
>
Hopefully this will help others diagnose if nothing else. Would love to hear others' insights towards a workaround or solution. If this is a bug on Apple's part, it seems like it might be worth filing a radar.
Apple Technical Developer support confirmed the issue. Submitted bug report. Will keep you posted.

Simperium Received an Invalid Change

I am developing integration with Simperium, the integration is complete and has been running on test machines for sometime, I am starting to get this error on one of the devices and it keeps repeating constantly any ideas?
MeetingPad[891:1103] Simperium error (ActionLinks82), received an invalid change for (a18852011efe4964a6fdeb1853c790f3)
2013-02-07 10:07:05:277 MeetingPad[891:1103] Simperium client ios-7f43b434754d882923e966df5d885755 received change (ActionLinks82) ios-4176925448fa8ae0a2f1d0937627aa6b: {
ccids = (
3f3b4550b23147d49e194038feea09a6
);
clientid = "ios-4176925448fa8ae0a2f1d0937627aa6b";
cv = 5112df4b37a401031dcc5be1;
ev = 2;
id = 9ca0b7ad04314ab9888d75691be784b5;
o = "-";
}
If this occured on a user account what would be the guidance?
One thing to check is that you're using the latest version of the code. The repository was recently open sourced on GitHub. There used to be a bug related to nil values that could cause an invalid diff to appear in your change stream, but it should be fixed now.
Assuming you're using the latest code though, does the error repeat continuously for the same "id" value, or are there different values?
Looking at the Simperium code where this error occurs, it's possible it could be displayed if you've deleted an object locally and remotely at around the same time. In your app, might ActionLinks fit that pattern, i.e. are you creating and deleting a lot of them across multiple clients?
If that is indeed the cause, then the error is innocuous and we should patch the code. Let me know what you discover.

Resources