Posts tagged with iOS

Debugging retain cycles in Objective-C: four likely culprits

Your iPhone app seems to be working fine. But suddenly, it starts to run slow and crashes! You suspect it's memory-related. Here's how to track down and fix issues with retain cycles, which can cause memory not to be released properly.

I'm assuming you are using ARC and iOS5.0+. While ARC simplifies a lot of memory management, it won't spot all retain cycles for you!

First, check you're genuinely dealing with a memory condition. Try to reproduce the crash on a device, and look for crash reports using Xcode > Organiser > Device Logs.

Low memory crashes don't look like normal crash reports, you won't see a stack trace! Instead you'll probably just see Process = Unknown, and the crash report will contain a list of processes which were running at the time of the crash.

Screen Shot 2013-04-16 at 14.01.59

In this example, you can see that Biblegram was using over 25000 pages of memory. 1 page of memory is 4KB, so that's 100MB of memory, which seems much too high.

Next, fire up Instruments via Product > Profile and select the "Allocations" template. Playing around with the app, you should easily be able to locate places where the memory allocation keeps going up and up.

Screen Shot 2013-04-16 at 14.12.13

This "staircase" pattern is a giveaway. I'm repeatedly pushing and popping one view controller, but the memory goes up and up.

Try putting a breakpoint in the dealloc method of the problematic view controller. Most likely, this will never get hit, showing that the view controller is never released.

Screen Shot 2013-04-16 at 14.30.22

This is a strong indication that we have a retain cycle. When the view controller is dismissed there are still some strong references to it, so it doesn't get dealloced.

As one last verification, enter the name of the class into the search box in the top right of Instruments, and check the "# living" column. This is showing 3, when we'd expect it to show 1.

Screen Shot 2013-04-16 at 14.15.38

You can drill down into the instances of the view controller by tapping the small arrow to the right of the class name, and drill down further to see every place that the view controller is retained and released.

This view can be rather overwhelming: the system frameworks do a lot of retaining and releasing on your behalf! For example, instantiating a NIB can easily increase the retain count to 40 temporarily.

So: here are four common errors to look out for that may cause your retain count to be higher than expected.

1. NSTimer

If you create an NSTimer object on a view controller, be sure that invalidate is called on it when dismissing the view controller, otherwise it will retain self.

2. Observers/NSNotificationCenter

If you add an observer to NSNotificationCenter, make sure you remove all observers when dismissing the view controller, otherwise they will retain self.

3. Blocks

You should not call [self doSomething] from inside a block, this can easily lead to the block capturing a reference to self. Instead, make a weak reference to self:

BAD:

  1.  
  2. dispatch_async(queue, ^{
  3. [self doSomething];
  4. });
  5.  

GOOD

  1.  
  2. __weak MyViewController *safeSelf = self;
  3. dispatch_async(queue, ^{
  4. [safeSelf doSomething];
  5. });
  6.  

4. Delegates

if you use

  1.  
  2. someObj.delegate = self;
  3.  

inside the view controller, check the delegate property on someObj is weak.

  1.  
  2. @property (nonatomic, weak) id delegate;
  3.  

Once you've made your fixes, check that dealloc is getting hit and the allocations no longer increase endlessly.

How to launch Google Maps or Apple Maps from an iOS app

Today Google announced their much-anticipated Google Maps app for iOS. They also plan to make available a Google Maps SDK for iOS allowing iOS developers to integrate Google Maps in their apps, however at present API keys are only available for selected developers.

If you want to provide your app's users with the ability to open a location in Google Maps, there is an option which works today: Google have added a comgooglemaps URL scheme to their app so third-party apps can launch the Google Maps app.

Assuming you'd like to give your users a choice of opening a location in both Apple or Google maps, you can set up some code like this in a new view controller:

  1. #import "ViewController.h"
  2. #import <MapKit/MapKit.h>
  3.  
  4. @implementation ViewController
  5.  
  6. - (void)viewDidLoad
  7. {
  8. [super viewDidLoad];
  9. // Add a button to pop open an action sheet
  10. UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
  11. btn.frame = CGRectMake(80,100,160,50);
  12. [btn setTitle:@"Open placemark" forState:UIControlStateNormal];
  13. [btn addTarget:self action:@selector(openActionSheet:) forControlEvents:UIControlEventTouchUpInside];
  14. [self.view addSubview:btn];
  15.  
  16. }
  17. -(void)openActionSheet:(id)sender {
  18. //give the user a choice of Apple or Google Maps
  19. UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"Open in Maps" delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:@"Apple Maps",@"Google Maps", nil];
  20. [sheet showInView:self.view];
  21. }
  22. -(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
  23. //coordinates for the place we want to display
  24. CLLocationCoordinate2D rdOfficeLocation = CLLocationCoordinate2DMake(31.20691,121.477847);
  25. if (buttonIndex==0) {
  26. //Apple Maps, using the MKMapItem class
  27. MKPlacemark *placemark = [[MKPlacemark alloc] initWithCoordinate:rdOfficeLocation addressDictionary:nil];
  28. MKMapItem *item = [[MKMapItem alloc] initWithPlacemark:placemark];
  29. item.name = @"ReignDesign Office";
  30. [item openInMapsWithLaunchOptions:nil];
  31. } else if (buttonIndex==1) {
  32. //Google Maps
  33. //construct a URL using the comgooglemaps schema
  34. NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"comgooglemaps://?center=%f,%f",rdOfficeLocation.latitude,rdOfficeLocation.longitude]];
  35. if (![[UIApplication sharedApplication] canOpenURL:url]) {
  36. NSLog(@"Google Maps app is not installed");
  37. //left as an exercise for the reader: open the Google Maps mobile website instead!
  38. } else {
  39. [[UIApplication sharedApplication] openURL:url];
  40. }
  41. }
  42. }
  43. @end

Here's the app in action, example code is on Github.

    

For more details, see the Google Maps URL Scheme documentation.

Building Flockwork: Creating targets for free and full versions in a single Xcode project

This is part in a series of blog articles explaining some of the interesting technical hurdles we encountered while building our new iPad puzzle game Flockwork.

    

We'll soon be launching a Free version of Flockwork, so I wanted to explain how best to set up your Xcode projects if you're planning to support two versions. The goals were:

  • 1. Keep everything in a single Xcode project
  • 2. Make it easy to share code and assets
  • 3. Keep file sizes of the compiled apps as small as possible

1. Setting up a second target
The first step is to duplicate your existing target (for example MyAwesomeGame). Right/command click the target and choose Duplicate.

OK, what did Xcode just do?

  • It created a new target called MyAwesomeGame copy
  • It added a new Info.plist
  • It autocreated a new scheme for building the project

First you'll want to rename things more sensibly. Press ENTER with the new scheme highlighted and name it MyAwesomeGameFree.

Next, go into Build Settings for the MyAwesomeGameFree target and filter for two settings: first change "Product Name" to MyAwesomeGameFree.

Next, change the "Info.plist file" setting to MyAwesomeGameFree-Info.plist.

Of course, now you'll also need to rename the file to match. You can also move it to a more sensible group, as Xcode dumps it in the root of the project by default.

Now is also a good time to modify the Info.plist, you may want to change:

  • the Bundle Identifier (to match whatever you created in iTunes Connect)
  • the icon files and default launch image
  • Bundle display name

Finally you can rename the autocreated scheme - go to Product>Manage Schemes and rename the scheme to MyAwesomeGameFree.

Time to test it works! Choose the correct scheme from the dropdown, and run!

2. Detecting the target at runtime

It's likely there won't be too much difference between your paid and full versions: for example in Flockwork, most of the game is similar, but we only have 20 levels in the free version versus 80 levels in the paid version. For places in the code where you need different behaviour, you can do a runtime switch. We defined a boolean like this:

  1. #define IsFree ([[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"] isEqualToString:BUNDLE_ID_FREE])

Then it's easy to write code like this:

  1. NSString *path = IsFree?@"levels_free.plist":@"levels_full.plist";

3. Selecting which assets will be included in each target

Now you have multiple targets, you need to be careful when adding new files to the project. You've probably seen this dialog many times. When you add a new file to the project, you'll now need to ensure the correct targets are checked: make sure both targets are checked if its a file used in both versions, but only one target is checked if its only used in one version.

What about files which are already added to your project? If you go to Build Phases>Copy Bundle Resources you can see a list of all the files compiled into the target. It's important to remove any you're not using. For example, leaving in a full-screen splash from the full versions in your free version could easily add 2MB to your final app size. If you're trying to get under the 50MB over-the-air download limit, that's a lot!

Flockwork is available now in the app store! If you'd like to know when we launch Flockwork Free, sign up to our mailing list at ReignGames.

What do all top-selling iPhone games have in common? It’s the controls, stupid.

How do you make a game which will be highly successful in the App Store? It's a question thousands of developers ask themselves daily. Is it the graphics? Is it the level design? Is it the emotions your game engenders? Is it the time-delay before you prompt the user to rate your app? Is it birds and pigs? Is it integrating with Facebook?

No. It's the controls, stupid.

Take a look at the apps which have topped the all-time bestseller lists.

Angry Birds. Fruit Ninja. Doodle Jump. Cut The Rope.

What did all these games do brilliantly? The gameplay of Angry Birds is not original. Throwing projectiles at enemies protected by collapsible structures had been done before, in games like Castle Clout and Armor Games's Crush the Castle. Those games were fun, but not mainstream.

It was primarily Angry Birds' intuitive slingshot controls which made the app into the multi-million dollar franchise it is today. The controls were very easy to grasp, and the act of dragging, releasing, and watching the ensuing destruction is satisfying and cathartic.

The core action of Fruit Ninja is a slicing action to chop fruit. The combination of the slicing gesture and corresponding "juicy" effects is also highly satisfying. It's the digital equivalent of popping bubble wrap.

Doodle Jump uses the iPhone's accelerometer as the controls. Tilting the iPhone side-to-side moves the character. It was a clever (and at the time of release, quite novel) use of the device hardware that made the game simple to pick up. If the game had used buttons instead, it would have been much less successful.

Cut the Rope also uses slicing controls, but for a puzzle game. A sign that a level-based game like Angry Birds and Cut the Rope is well-designed is that even replaying the same level again and again is still fun, because performing the core game actions is satiating.

So what do all these game control systems have in common

  • 1. Easy to pick up
  • 2. Satisfying to perform

What doesn't work so well? Games ported from other platforms often use 'virtual joysticks' or buttons.

Players need to feel that their touches are having a direct effect on the action, after all this "direct manipulation" is a key part of Apple's iOS experience. Playing a football game, I don't want to use buttons or joysticks to move the players, I want to feel like I am directly controlling them.

Grand Theft Auto III is one of the best reviewed console games of all time. It has a complex plot and a massive open world. But on iPhone and iPad it just feels fiddly. You have to poke at virtual buttons on the screen. It ranks reasonably well in the App Store, but is easily beaten by games with better controls.

Other games are over-reliant on taps. One-touch controls can work really well (see: Tiny Wings) but other games which follow a "touch everything which moves!" metaphor aren't so interesting.

So: want to make a top-selling iPhone game? Make your controls easy to pick up and satisfying to perform. If you can do that, AND do something unique, you could have a hit. (Of course you'll still need great graphics, music, integration, level design, and so on: but if you don't have great controls, all of those may be in vain)

When developing our new game Flockwork, we think we've created a control system which is easy and satisfying, but also unique.

Everyone quickly understands how to drag their finger on an iPad screen.The twist is that in Flockwork, when you drag, you don't just move one sheep character, but all the sheep at once. This turns a simple puzzle game into something much more interesting - since you can't control the sheep individually, you have to use all the objects in the game environment to help get the sheep to the targets. Watch our trailer below for more:

And if you'd like to sign up to receive an email when Flockwork is launched, visit our Flockwork microsite.

Sh*t iPhone Developers Never Say

Last month I attended Shanghai Barcamp 10. It's become something of a tradition for me to give a talk on a slightly offbeat topic at Barcamp, whether it's Love Hotels and Unicode or HTML5 and sarcasm.

Now, you're probably aware of the recent popular internet meme Sh*t Girls Say and its spinoffs like Sh*t Asian Girls Say.

 

The meme quickly got more and more specific.

As I am neither a Jehovah's witness, nor a Asian girl (well, except on Friday nights) I thought I'd try something a little closer to home, so: what phrases do iPhone developers never say?
First up,

Continue reading...