Some of you may have noticed that we released Lost Cities in the end of August. Apart from being our first card game (we did Carcassonne prior to that), it is also our first time to use the Turn Based API that Game Center provides starting with iOS 5.0. This sadly has proven a very challenging thing to do, and I want to part with some of the major gripes we encountered in this multipart series.
Game Center’s idea of Push Notifications
Push notifications are something iOS users are pretty familiar with now. They also made the hop to the desktop in Mountain Lion. Every app can send push notifications to the user in appropriate moments, if the user gives their permission. Users can even customize them on a per app basis and set if they want them to appear as alerts, banners and/or in the lock screen, if they have sound or not or they should badge the App Icon.
All right, these are nice and understandable, already a little bit complicated – but okay. And if you are using the Game Center Turn Based API you get push notifications too. Isn’t that nice? Or so it seems. The Game Center Push Notifications sadly have some strangeness to them:
They aren’t yours – meaning they do not belong to the app sending it. They appear with the Game Center Icon and your App’s Name. The push notifications appear in Quotes to discern them from other Game Center Notifications. If you can search the web for undocumented features you can change the built in fanfare to your app’s sound. That’s it. (rdar://10177685)
They are all or nothing – your users can’t set the notification settings for your app, they have to set the one’s for game center. You read right. You can’t turn them on or off for specific games, or even discern them from the Game Center’s friend requests. You want a Sound for this game notification? Then you also have to live with the friend request fanfare. You have a game you play only on your iPhone and not on your iPad? Too bad for you, can’t tell the iPad not to show the notifications. (rdar://10177685)
Invitations to games appear during your game - yes that too. If you are invited to a new game, while already in that game’s app, you get the invite notification banner shown inside your app. Also: there is no sane way of showing the invite text to you other than show in notification center, as we the app devs don’t have access to it. We don’t even get a notification.
No extra payload and early access for you - with normal push notifications you, as an app, can add a little bit of extra payload to that notification which you get instantly when your app is moved into foreground or started. This way you can prepare the correct UI for the Notification the user swiped upon while you sneakily load the data you need in addition that in the background to give the fastes experience. Not so with Game Center. With Game Center you aren’t told at all that the user swiped on a notification to start your app. The earliest you can guess that the user swiped a notification is in the turn based delegate callback, which only fires after the Game Center Greeting Banner (you know the one) is shown in your app, which can be up to 30 seconds in bad networking conditions. And which also fires if you start the app and a turn is received during that time. So we more or less have to guess if the user swiped a notification instead of knowing. (rdar://10177254)
They aren’t localizable – yes you read right – there is no sane way of localizing the Notifications. There isn’t even a proper way to set the notification text directly. (rdar://9581651)
You can either chose to not set a custom text in which case the recipient(s) of the push notification get a message of “Your Turn” localized in their iOS Language. But then you have no additional information about the game in progress, no opponents, no turn order, no score, no whatsoever.
Or the participant that made the last move may set a text to be sent instead. This is the text that will be displayed in quotes in the notification. E.g. we chose to set “20 cards against Monkeydom”. This is the Status text of the game. Which poses a problem if you use the standard Game Center Turn based UI, as it appears in that list as status of the user that made the turn. So if you are using that you already have the issue of a strange display.
However if you want to go the extra mile and localize the Message then the only thing you can do is the following: The participant app that makes a move needs to anticipate the recipient of the push notification (e.g. the next player), localize the notification in their language, and set the text and make the move. This needs knowledge about the locale of the recipient, which in turn you have to transport in the game data. We chose that approach, but it was a serious pain.
Although I’m sure some of the issues are adressed in iOS 6, I already know of some that aren’t. Which in turn means one more year until we can hope for improvements again.
Just to go on the record: If and when an iPad mini comes I predict it will have the 1024x768 @1x resolution cramped into that smaller display area. It will have slightly higher DPI than the original iPad/iPhone, but it won’t be Retina. All iPad apps will just work. All iPad apps that disregarded Human Interface Guidlines and made their buttons too small will have issues. All others will be a little bit more intricate to handle than on the iPad/iPad2, but be fine overall.
There is a dilemma Game Developers face on iOS in terms of synced game state across multiple devices. This also applies to games that you deleted from the device once and want to continue playing some time in the future.
Apple’s recommended solution is to use the iCloud. However the iCloud storage is not tied to your Game Center account. This causes some major problems:
- somebody logged into Game Center on a device that does not have his iCloud account simply doesn’t get his/her save games / game state (little known fact: you can log into the game center on any device using your account)
- since the iCloud account and the Game Center account are totally independet, there is no way of telling the user about this fact gracefully, so you end up just creating a new game state for this game center id on this iCloud storage, everyone is confused
- you essentiallly have to design your app around the fact that the user might have an iCloud account or not, and might have a Game Center account and not, and any combination of iCloud account and Game Center account, and work fine and predictable in every case. This adds very uneccessary complexity to quite a simple problem: taking care of the users progress in the best possible way.
However, there would be a simple solution to this dilemma: make Game Center provide storage that is tied to the Game Center account. That is the natural fit. Users would get what they expect. If you think so too, please file a duplicate of rdar://11263793 so we get this rather sooner than later.
Sadly, this is just one of the little details Apple did not figure out right with Game Center.
PSA: I switched to dot syntax for Singletons now. Seems fitting, looks less cluttered. I took some time getting used to it, but now I really like it.
If you put in network code into your apps, it is always important to test against bad network conditions. While this isn’t too trivial, it was always possible using dummynet.
However, now new in Lion is a Preference pane to use, so you don’t have any excuses anymore for not testing: Network Link Conditioner - Use it and use it heavily - real life network conditions, especially on iOS, are never as good as in your pristine office big bad ass network location.
Today I did a little mind experiment thinking about if an ARM based MacBook Air would be viable, and what would be the pros and cons.
- Battery life
- Return to in house chip development
- iOS and Mac running on the same architectures
- Probably a good long term bet, as ARM is here to stay and winning the performance per watt battle
- No Bootcamp, parallels or the such
- No Rosetta-like intel emulation for existing apps
- Probably less performant, although with enough Cores going on, probably not that big of an issue.
However the more interesting point to me is that Apple is here to innovate. Mac OS X has kind of seen its time and iOS in itself is a good companion, but not a replacement. If they really want to change the way the standard computer works then a mandatory transition to another hardware platform could be the way.
How could such a future look like:
- it would be one big step further in the direction Lion is taking. But since backwards compatibility is not an issue there, as developers would have to recompile and adjust to the hardware anyways, Apple could cut with old metaphors and really innovate. E.g. away with the standard file system. Away with the unix shell. Away with the Menubar. Keep technologies and infrastructure, but only expose the Developers to the new environment.
- redo AppKit from ground up, gone with the slapping on of technologies adapted from the iOS development, a real first class DesktopUIKit, give it the same love that the rewriting did for iOS, perfectly adapted to the new and improved interface metaphors.
- the platform would be closed in the same way that iOS is closed. Apps only via the App Store, nothing else. There is a clear incentive for Apple to do this, 30% of every sale made.
- the platform would include full support of iOS and iPad Apps out of the box. E.g. the platform would be comprised of a multiple of the iOS/iPad resolutions so multiple iOS apps could be used at once
- great integration of multitouch, both via the trackpad and also directly on the screen.
In essence Apple would create a third platform between iOS and Mac and use it as an opportunity to implement major changes in the user interaction model. It could leverage the existing Developer base in the same way, or even more so than they did with iOS.
The major flaw in that theory: This third platform really needs a good incentive for users to jump on. Something as “magical” as the iPad had. And I have no clue what that might be, but hey, that’s just me, I also thought that Apple needed something special for an iPad to make sense. And all they needed was a great execution of the basic features, reduced to the max to chime in the Post-PC era. The next step: to make the Mac make the jump into the Post-PC era as well.
So these are my 2 cents, here to stay to be laughed at in the future ;)
Let’s begin with the basics. In iOS there is a watchdog process - a watchdog that checks if an App takes more than about 20 seconds to start without getting back to the OS it will be killed. In the crash reports this killing is indicated by an exception code of 0x8badf00d - Ate Bad Food. Getting back to the OS more or less means that your delegate call of application:didFinishLaunchingWithOptions: needs to return control to the runloop in time. If you don’t: you are killed. The user experience of this is that your Default.png is shown for about 20 seconds, and then the iPhone/iPad returns to the home screen.
Fun Fact: On iOS Devices that support multitasking the watchdog can be confused by pressing home and go back to the app again in intervals slightly less than 20 seconds. So if an App is showing this behavior, you might get it to work again using this stunt.
This is all good and well, the user should not have to wait for more than 20 seconds for an App to actually do something that the user can see or interact with. This is where Core Data comes in.
Core Data is a great framework that provides developers an abstraction to a local database. You do so by specifying a data model in Xcode, and provide classes for your database objects. In code you usually initialize your Core Data infrastructure very early on, because you rely on the data in the database for almost everything your app does. Usually that step takes little time - if the app is new you maybe want to copy an existing database as a first step, otherwise it just opens up the database files and returns.
However, if you want to store additional information in your database, you need to update your model. And if you do so, you need to migrate your data once to accommodate the new model. This happens in that early on initialization step. As migration goes there are two kinds of migration: Lightweight migration and manual migration. Lightweight migration is fast and can be done by just issuing SQL statements to the underlying database. Manual migration causes every object to be loaded and be updated. If you can achieve your changes with lightweight migration, especially on iOS devices you should do so. However, sometimes it is necessary to do some manual work in the migration process.
This is where the shit can hit the fan: Consider you are initializing Core Data before the end of application:didFinishLaunchingWithOptions: because you want to use some of the existing data to prepare badges and UI state in the first viewWillAppear: methods of your top view controllers. Consider further that you are making a major update to your App and the database needs a manual migration. Now what easily can happen is that the watchdog literally bites your app in the ass.
And even better, during development you probably won’t notice. Why? Because of many reasons, I try to list many of them:
- The watchdog is inactive if you are running in the debugger. Your App Start may take a while, but your app will always start up.
- The migration step only happens once, unless your specifically testing for it over and over again. And it is most likely that that step is done while you are running in the debugger to check if everything goes right.
- The data you need to test needs to be big. In most test and development scenarios you have realistic data, with a wide variety of items being in there to catch most cases, but you won’t do very big data because that slows down overall roundtrip time.
- Your test devices most probably will be the fastest you have most of the time, again for roundtrip reasons. And faster devices migrate faster. Probably fast enough to not show the problem.
When you look at the customers that will have that problem out there you see another problem: They are probably your best customers. If they store a lot of data in your app, they use it a lot. If there is a lot of data, migration takes long. Tada! Horrible.
So now that we have established that this problem is a real turdball coming your way, how do you prevent/fix it?
Solution #1: do lightweight migration. If you can do that. This will be fast enough to not call the wrath of the Watchdog.
Solution #2: if you can’t do lightweight migration, make sure your migration step won’t be caught by the watchdog. E.g. your initialization of Core Data needs to be done after the app has returned from the application:didFinishLaunchingWithOptions: callback. A really good way of testing this is adding a simple sleep(25) in the code that actually creates your persistentStoreCoordinator. That way without having all test data, migration always takes long enough to trigger the watchdog. And again be sure to not attach the debugger, or the watchdog won’t bite.
- Beware of the watchdog.
- Core Data is great, but migration can really hit you quite unexpectedly.
- If you use Core Data - put in that sleep(25) call now and test if you get bitten. If so fix it so it won’t bite you unprepared in the future when you update your model.
- Prepare feedback for user migration. As it turns out users don’t make a difference between an app that appears frozen for a long period of time and an app that actually crashes. Even if migration will only happen once for each major model update, that moment can be crucial.