iPhone @2x Graphics, scale, and iPad
The iPhone 4’s retina display has 2x the pixel pitch of the previous iPhones, giving the screen 4x as many pixels. It looks fantastic, and with dimensions exactly twice the original screen it means that the phone can pixel double graphics in apps that haven’t been updated yet, and it looks fine (at least until you get used to using high resolution apps, and then its hard to go back). Apple gave developers an easy way to adapt apps to work with either screen. For UIKit based apps, if a view loads a graphic ‘myButton.png’ for the original resolution, then just by adding a file named ‘myButton@2x.png’ to the project in Xcode means that on iPhone 4 the app will load the @2x file instead. Simple and effective.
Detecting iPhone 4 / Scale
For graphics that aren’t loaded as a project resource, you might need to programmatically determine if you want to draw or load @2x resolution or normal resolution. To determine this, you could use something like:
+ (BOOL *) isIPhone4 { return ([MyDeviceClass platformCode] isEqualToString:@"iPhone3,1"]); } + (NSString *) platformCode { size_t size; sysctlbyname("hw.machine", NULL, &size, NULL, 0); char *machine = malloc(size); sysctlbyname("hw.machine", machine, &size, NULL, 0); NSString *platform = [NSString stringWithCString:machine encoding:[NSString defaultCStringEncoding]]; free(machine); return platform; }
However this isn’t going to work on the simulator, iPad, or future devices. Instead, use the scale property of UIImage or UIScreen. Older versions of iOS don’t have this property, so you have to test that its available before use:
+(BOOL) screenIs2xResolution { return 2.0 == [MyDeviceClass mainScreenScale]; } +(CGFloat) mainScreenScale { CGFloat scale = 1.0; UIScreen* screen = [UIScreen mainScreen]; if ([UIScreen instancesRespondToSelector:@selector(scale)]) { scale = [screen scale]; } return scale; }
Scale on the iPad
So now you can code conditionally for iPhone 4’s retina display, but what about the iPad running iOS 3.2? iPad can run iPhone apps at normal 1x size, or pixel doubled 2x size. When running 2x pixel doubled on the iPad, iOS3.2 will automatically use real screen pixels if its drawing UIImages and hence draw them at full resolution, which is a handy compromise. This _doesn’t_ work for iOS4 targetted apps built with the @2x file names :-( but of course we can assume this will come to the iPad in the fall with iOS4.
iOS3.2 on the iPad does have support for the scale property. So if you want higher resolution graphics loaded programmatically when your iPhone app is running on the iPad, the code above will work. There’s a catch though. When running an iPhone app in 1x mode on the iPad, scale returns 1.0, and when running in 2x mode scale returns 2.0. The first time someone uses your iPhone app on their iPad they might switch between 1x and 2x few times to decide which they prefer. So you could end up loading a mix of normal and 2x resolution images. If you are loading images from the net and caching them (very common in the apps we build), then you might end up drawing the low resolution image from cache even in @2x mode. In that case, a specific test for the iPad might be in order too when deciding which resolution to download and cache, so you always have the 2x files ready for the iPad:
+(BOOL) isIPad { BOOL isIPad=NO; NSString* model = [UIDevice currentDevice].model; if ([model rangeOfString:@"iPad"].location != NSNotFound) { return YES; } return isIPad; }
Xcode on a 2010 Macbook Pro High Res with SSD
I just bought a new 2010 15″ Macbook Pro and put in a solid state drive - OWC Mercury Extreme Pro SSD 240GB. It’s to replace my 2007 macbook used all the time for iPhone development with Xcode. I picked the hi-res antiglare screen (1680 x 1050). The OWC SSD is a new Sandforce based model
that is reported to not suffer from the precipitous drop in write performance common to many SSDs on the market. (I also considered using a Seagate Momentus hybrid drive
.) Benefits of the new machine include:
- Faster CPUs and hyperthreading, so CPU bound Xcode builds gets to run 4 parallel compiles at the same time for a big speed boost.
- The SSD has blazing random access times thus speeding up all general use of the machine, including the compile-deploy-test cycle with Xcode and the iPhone simulator.
- More screen space makes it easier to work with Xcode and the iPhone & iPad simulator on the road.
- The machine can better keep up with demanding Instruments profiling.
Simple Benchmarks
Here are some simple stopwatch based benchmarks comparing new and old machines. Its much snappier all round, and definitely speedier for development work.
| 2007 Macbook Pro 2.16GHz | 2010 Macbook Pro 2.4GHz i5 | ||
| 7200rpm HD | 5400rpm HD | SSD | |
| Boot, from power on to desktop first appearing | 30s | 40s | 24s |
| Launch Xcode | 7s | 6s | 4.5s |
| Build complex iPhone project from clean | 86s | 48s | 45s |
| iPhone sim & Xcode install & debug app | 10s | 9s | 3s |
| Instruments open 30MB trace | 11s | 6s | |
| Start Photoshop CS3 (1st time, 2nd time) | 15s / 5s | 5s / 4s | |
High-res Display
The high-res display gives more pixels within the same space, so GUI elements take up less space and fonts draw smaller. This means Xcode and the simulator fit on the high res display. For me they didn’t quite fit right on the normal display, so I always found myself re-arranging windows when working out of my office (where I have a 27″ desktop display). You can see this in the two screen shots below, the first from the old macbook and below that the new high-res macbook. I boosted the font for code from Monaco-12 on old macbook (also great on my desktop display) to Monaco-14 to get the same sized text on the high res macbook display. I might settle on a 12 or 13 though because the new screen is much clearer than the old one. Fortunately Xcode makes it easy to change font sizes for code so switching between 12pt for desktop and 14pt when on the road shouldn’t be an annoyance.

Open-GL on Simulator
I was hoping that the faster CPUs would boost performance of the iPad simulator running OpenGL. OpenGL on the simulator has terrible performance, I guess this is because the simulator is doing all its graphics with CPU based emulation of the graphics hardware on the phones, or maybe it just doesn’t use real graphics hardware on the macbook. On the one hand thats fine for performance work because performance work of course has to be done on real devices. However for general game and graphics logic development it means that running the game on the simulator (especially in iPad mode) means that frame rates are really low, lower than a generation 1 iPhone. The new macbook didn’t really help much here, OpenGL on the simulator still pegs one CPU on the macbook, and frame rates really didn’t improve much.
Xbench Tests
| Result: | 266.92 | ||
|---|---|---|---|
| System Info | |||
| Xbench Version | 1.3 | ||
| System Version | 10.6.4 (10F569) | ||
| Physical RAM | 4096 MB | ||
| Model | MacBookPro6,2 | ||
| Drive Type | OWC Mercury Extreme Pro SSD | ||
| CPU Test | 189.95 | ||
| GCD Loop | 296.65 | 15.64 Mops/sec | |
| Floating Point Basic | 166.76 | 3.96 Gflop/sec | |
| vecLib FFT | 109.83 | 3.62 Gflop/sec | |
| Floating Point Library | 386.71 | 67.34 Mops/sec | |
| Thread Test | 465.08 | ||
| Computation | 483.74 | 9.80 Mops/sec, 4 threads | |
| Lock Contention | 447.80 | 19.26 Mlocks/sec, 4 threads | |
| Memory Test | 300.03 | ||
| System | 320.11 | ||
| Allocate | 469.76 | 1.73 Malloc/sec | |
| Fill | 214.25 | 10417.48 MB/sec | |
| Copy | 388.24 | 8018.86 MB/sec | |
| Stream | 282.32 | ||
| Copy | 281.48 | 5813.81 MB/sec | |
| Scale | 278.43 | 5752.27 MB/sec | |
| Add | 287.15 | 6116.93 MB/sec | |
| Triad | 282.38 | 6040.76 MB/sec | |
| Quartz Graphics Test | 243.53 | ||
| Line | 191.89 | 12.78 Klines/sec [50% alpha] | |
| Rectangle | 244.42 | 72.97 Krects/sec [50% alpha] | |
| Circle | 216.59 | 17.65 Kcircles/sec [50% alpha] | |
| Bezier | 207.61 | 5.24 Kbeziers/sec [50% alpha] | |
| Text | 557.04 | 34.85 Kchars/sec | |
| OpenGL Graphics Test | 192.35 | ||
| Spinning Squares | 192.35 | 244.01 frames/sec | |
| User Interface Test | 326.40 | ||
| Elements | 326.40 | 1.50 Krefresh/sec | |
| Disk Test | 321.71 | ||
| Sequential | 192.97 | ||
| Uncached Write | 282.94 | 173.72 MB/sec [4K blocks] | |
| Uncached Write | 278.32 | 157.47 MB/sec [256K blocks] | |
| Uncached Read | 92.01 | 26.93 MB/sec [4K blocks] | |
| Uncached Read | 365.84 | 183.87 MB/sec [256K blocks] | |
| Random | 966.64 | ||
| Uncached Write | 1186.91 | 125.65 MB/sec [4K blocks] | |
| Uncached Write | 517.95 | 165.82 MB/sec [256K blocks] | |
| Uncached Read | 2940.24 | 20.84 MB/sec [4K blocks] | |
| Uncached Read | 975.84 | 181.07 MB/sec [256K blocks] | |
iPhone Memory Debugging with NSZombie and Instruments
When your iPhone app crashes with ‘BAD ACCESS’ you’re in trouble – a memory bug where you tried to call a method on a object that was already deleted. Instruments has support for NSZombie – a feature that makes it easy to find the source of the bug by showing you a full history of every alloc, retain, release, and autorelease of the object that caused the crash! Wow. Here’s how:
Basic steps are:
- Run in Performance tool ‘Object Allocations’
- Stop running and set options on ObjAllocations instrument: ‘Enable NSZombie’ Detection and ‘Record Reference Counts’
- Re-run from instruments, when it crashes, click the arrow in the popup zombie dialog
- Open up the stack trace view and see the full memory history of the problem object
- Wow, how awesome is that?
- If you love this post, do me a favor and check out our Free app Focus for Facebook
- You might also like my memory management tutorial and other posts under ‘App Development’.
Here’s a link to the demo program: ZombieDebug Demo Project. The code we’re looking at in the video is:
@implementation ZombieDebugViewController @synthesize objArray; -(void)rewriteText { NSMutableString* s = [NSMutableString stringWithCapacity:100]; for (id obj in objArray) { [s appendFormat:@"%@,\n",obj]; [obj release]; } label.text = s; } - (void)viewDidLoad { [super viewDidLoad]; self.objArray = [NSMutableArray arrayWithCapacity:10]; [objArray addObject:@"I'm a string object"]; [self rewriteText]; } -(IBAction) tapButton:(id)button { NSNumber* n = [NSNumber numberWithLong:random()]; [objArray addObject:n]; [self rewriteText]; } -(void)dealloc { [super dealloc]; self.objArray=nil; } @end
Debugging Tip – objc_exception_throw breakpoint
If an exception is thrown when debugging an iPhone app, without your own exception handling code, that exception won’t stop the debugger until the call stack has totally unwound. On that journey through the call stack it gets caught and disguarded in the event loop. That’s a bummer because then the debugger can’t show you where original exception was raised. This problem is easy to fix by adding a symbolic breakpoint for the runtime’s objc_exception_throw function, which is called as soon as the exception occurs. Here’s how:
1) Run the app in the debugger. There’s an exception we can see in the debugger console:
2) Open the debugger from XCode menu Run->Debugger. The stack we can see where the debugger paused is the useless stack where the event loop code caught the exception and then bombed out with its own exception.
3) From XCode menu Run->Manage Breakpoints -> Add symbolic breakpoint we add ‘objc_exception_throw’
4) Now when we debug run again, the debugger stops as soon as the oringinal exception was thrown, we can see exactly where, and we can poke around an inspect variables etc.
Memory Management Basics Tutorial Video
This article is a screen cast video of my tutorial for beginner iPhone programmers, it’s about the basics of memory management in Objective-C. Memory management is a tough nut for the beginner to crack, particularly in Objective-C and Cocoa for iPhone. Check out my iPhone memory management reading list for more voices on memory management.
The tutorial covers: Objective-C object retain counts; using retain, release, and autorelease; explains the autorelease pool in detail and how it works with the event loop; rule of thumb for if an object is in the autorelase pool.
Part 1: retain counts
Part 2: auto-release pool and the event loop
Part 3: auto-release pool and the event loop with retain
Part 4: auto-release pool wrap up
Part 5: properties, dealloc, bugs, auto-release rule of thumb
Thanks for watching!












