Saturday, August 18, 2012

Debugging with ARC and Zombies enabled

If you tried to debug your app with zombies enabled under ARC, you may have noticed that you don’t get zombies anymore. That’s most probably because of a bug in the Foundation framework that affects iOS 5 and OS X 10.7. This bug prevents ARC from "cleaning up" instance variables at deallocation-time according to Apple Technical Q&A QA1758. Apple strongly encourages you to run your app on an iOS 6+, or OS X 10.8+ system when debugging with Zombies.
In the meantime, you can workaround this bug if you are using ARC and trying to debug zombies on older systems. Just compile this NSObject+ARCZombie.m file in your project and ivars will be automatically deallocated under ARC when zombies are enabled, as it should be.
Now you might think Hey, you are swizzling dealloc, isn't that dangerous? Well, yes it is, but note that the dealloc method is only swizzled when the NSZombieEnabled environment variable is enabled, so even if this code slips in your release build, il will cause no harm at all.

UPDATE: After I posted this, Greg Parker warned me on twitter:

@0xced That'll cause over-release of associated objects on some systems.

So use this workaround with care and remember that the best solution is what Apple recommends: run your app on an iOS 6+ or OS X 10.8+ system when debugging with Zombies.

Wednesday, August 08, 2012

Prepare your apps for the iPhone 5

Earlier today, my friend Peter Steinberger asked:

Does anyone know what does “special iPhone Simulator tweaks” are? I’d love to test my stuff with different resolutions. http://9to5mac.com/2012/08/07/upcoming-ios-6-is-scalable-to-taller-640-x-1136-iphone-display-shows-possible-next-generation-device-user-interface/

So I investigated, and found a pretty elegant solution. Without further ado, here is how to change the size of the iOS simulator in order to test your apps in resolutions never seen before.

  1. Download File.txt into ~/Library/Application Support/iPhone Simulator (don't change the name of File.txt)
  2. Edit /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Applications/iPhone Simulator.app/Contents/Resources/Devices/iPhone (Retina).deviceinfo/Info.plist and add the following keys:
    <key>eagle</key>
    <string>640</string>
    <key>giraffe</key>
    <string>1136</string>
  3. Add setenv("CLASSIC", "0", 1); just before UIApplicationMain in your main.m file in order to support any simulator version.
  4. Quit the iOS Simulator app

You are now ready to test your apps on the rumored 640×1136 new iPhone.

This hack works on Xcode 4.4.1+ and the iPhone 5.1+ Simulator with the iPhone (Retina) device.

Thursday, May 26, 2011

How to restore Apple Front Row Trailers

English version below

Comme vous avez pu le constater, Front Row Trailers ne fonctionne plus. Étant donné que Front Row est absent de Lion, je ne mettrai plus à jour Front Row Trailers.

Voici donc la méthode pour remettre les bandes-annonces originales d'Apple:

  1. Téléchargez et décompressez Restore Apple Trailers
  2. Double-cliquez sur le fichier Restore Apple Trailers.command
  3. Entrez votre mot de passe


As you probably have noticed, Front Row Trailers is not working anymore. Since Front Row is missing in Lion, I have no plan to update Front Row Trailers.

So here is how to restore factory Apple trailers:

  1. Download and decompress Restore Apple Trailers
  2. Double-click on Restore Apple Trailers.command
  3. Enter your password

Monday, February 14, 2011

Using Ingredients as Xcode documentation browser

Xcode 3.2.3 documentation browser was quite broken: ⌘ + ⌥ + double click on a method or function would open the documentation browser but would not scroll to the method you just asked documentation for. So I started to write an Xcode plugin to open Ingredients instead. Then Xcode 3.2.5 was released (I skipped version 3.2.4) and this annoying bug was fixed, so I did not bother to publish my plugin. But recently, I read on twitter that [RED4CTED] documentation browser was even more broken, so I decided it was time to release my Xcode-Ingredients plugin.

Note that the plugin is written for Xcode 3, so things have probably changed in [RED4CTED] (which I have not yet tried). If you are brave enough to use [RED4CTED], that you know a bit about Xcode plugins (@kodz, @rentzsch, @tjw?) and that you would like to use Ingredients for reading the documentation, please have a look and adapt the plugin for [RED4CTED]. In order to discover the searchForAPIString: method, I just set a breakpoint on makeKeyAndOrderFront: and looked at the backtrace, but I'm not sure if this technique is still applicable.

I will update this blog post as soon as someone can get the plugin to work with [RED4CTED].

Saturday, September 11, 2010

CLLocation getDistanceFrom: vs distanceFromLocation:

In Backwards Compatibility if Apple Starts Polishing, Oliver Drobnik explains how he solved the getDistanceFrom: vs distanceFromLocation: problem. His solution involves a new method (distanceBetweenLocation:andLocation:) and eight lines of code for calling getDistanceFrom: through a NSInvocation.

Wouldn't it be nice if instead you could use [aLocation distanceFromLocation:anotherLocation] everywhere in your code, while still retaining backward compatibility? Enter the Objective-C runtime! In your main.m file, first #import <objc/runtime.h> and at the very beginning of your main function, add this:


Method getDistanceFrom = class_getInstanceMethod([CLLocation class], @selector(getDistanceFrom:));
class_addMethod([CLLocation class], @selector(distanceFromLocation:), method_getImplementation(getDistanceFrom), method_getTypeEncoding(getDistanceFrom));


Here you go, -[CLLocation distanceFromLocation:] available at runtime in any iOS version with only two lines of code. Note that on iOS 3, class_addMethod will add the distanceFromLocation: method to the CLLocation class using getDistanceFrom: implementation. On iOS 4, class_addMethod will do nothing as the method already exists.

Friday, July 02, 2010

Using SDK 3.1.3 with iPhone SDK 4

In iPhone SDK 4 Apple has removed all 3.x SDKs. I think that was not a very good idea to say the least. I can understand Apple wants developers to adopt the new features of iOS 4, but breaking zillions of Xcode projects is probably not a good way to win the hearts of those developers. Well, this is probably not Apple's goal anyway.

Sure there is a way to get your projects working again with the iPhone SDK 4, just set the Base SDK of your project to iOS 4:

Base SDK 4

and set the Deployment Target to iOS 3:

Deployment Target 3

This will get your projects compile and run again on both iOS 3 and 4. That's fine. Well, except when you want to make sure your project do not use any new iOS 4 only API.

The problem is, code like this will now compile without any warning or error:

[[NSUserDefaults standardUserDefaults] setURL:defaultURL forKey:@"DefaultURL"];

It's easier than you think to use a shiny new API without even noticing you are using something not available on iOS 3. This setURL:forKey: method is new in iOS 4 so this code will fail with an unrecognized selector exception when run on iOS 3. Unfortunately, the only way to catch this kind of oversight is to run it on an iOS 3 device. Or you can read the documentation of every single method and function you are calling to make sure it was not introduced in iOS 4. But this does not seem very reasonable, does it? The tools should help us detecting these problems at compile time, not runtime!

Here is how to get Xcode 3.2.3 and iPhone SDK 4 working with SDK 3.1.3.

  1. Log in to the iOS Dev Center
  2. Download the iPhone SDK 3.1.3 into ~/Downloads
  3. Run the install-iphone-sdk-3.1.3.sh script
  4. Quit and relaunch Xcode
  5. Duplicate your Debug configuration and rename it to SDK 3.1.3 Check
  6. Set the Base SDK of this new configuration to iPhone Simulator 3.1.3
  7. Add a User-Defined Setting GCC_OBJC_ABI_VERSION and set its value to 1 (one)
  8. Select GCC_OBJC_ABI_VERSION and choose Add Build Setting Condition from the gear pop-up button at the lower left of the window.
  9. Select Any iPhone Simulator and Any Architecture

There you go! By selecting the SDK 3.1.3 Check configuration, you get compile time check for misusing iOS 4 only APIs when targeting iOS 3.

Note that compiling with the iPhone Simulator 3.1.3 SDK and running in Xcode 3.2.3 simulator is not supported. What you get with this hack is just a compile time check of the APIs you are using. So do not expect to run your app on a 3.1.3 simulator.

Wednesday, June 02, 2010

Fixing -[NSMutableURLRequest setValue:forHTTPHeaderField:]

UPDATE

This problem is fixed as of Mac OS X 10.8.3+ and iOS 5.1+. I have no idea when it was actually fixed since radar sucks so much.

The problem

From NSMutableURLRequest setValue:forHTTPHeaderField: documentation:
In keeping with the HTTP RFC, HTTP header field names are case-insensitive.

I see three problems with this.
  1. Assuming all HTTP implementations are RFC compliant is foolish to say the least.
  2. Enforcing case-insensitivity is nonsense.
  3. This bit of documentation is accurate, the case of header fields is actually changed.
Trying this
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.example.com"]];
[request setValue:@"MyValue" forHTTPHeaderField:@"MyField"];
NSLog(@"result: %@", [request allHTTPHeaderFields]);
outputs
    result: {
Myfield = MyValue;
}
Notice how the 'F' of MyField was lowercased, that sucks! If the HTTP server you are trying to communicate with is case-sensitive, you are screwed. I filed radar #8029516, which is a duplicate of radar #3131623, which means there is almost no chance to have this fixed anytime soon.

When this happens, you should contact the server administrator asking for a case-insensitive implementation. But in the meantime, if you badly need to preserve the case of your headers, read on.

The investigation

First, let's fire otx to disassemble the Foundation framework in order to have a look at setValue:forHTTPHeaderField: implementation. I strongly suggest you to use otx version from subversion trunk, as it is better at resolving symbols in tail call optimizations (fixed in r553).

Excerpt from otx 0.16b:
   +18  [...]  movl     %eax,0x08(%ebp)
+21 [...] leave
+22 [...] jmpl 0x002c52bb

Excerpt from otx trunk:
   +18  [...]  movl     %eax,0x08(%ebp)
+21 [...] leave
+22 [...] jmpl 0x002c52bb _CFURLRequestSetHTTPHeaderFieldValue

Much better, isn't it?

So let's go through the function calls of setValue:forHTTPHeaderField:. With a basic static analysis of the Foundation and CFNetwork disassemblies, we can draw the following call path:
-[NSMutableURLRequest(NSMutableHTTPURLRequest) setValue:forHTTPHeaderField:]
CFURLRequestSetHTTPHeaderFieldValue()
URLRequest::setHTTPHeaderFieldValue()
HTTPRequest::setHeaderFieldValue()
CFHTTPMessageSetHeaderFieldValue()
HTTPMessage::setHeaderFieldValue()
_CFCapitalizeHeader()

_CFCapitalizeHeader looks like a very good candidate for being the bastard changing the case of our headers. A quick search reveals the source code of CFNetwork that was open source long time ago. Although the open source implementation does not exactly match what we see in the disassembly (it is now using toupper instead of ch + 'A' - 'a' for example), we are now absolutely sure that _CFCapitalizeHeader is the function responsible for messing with our headers.

Now, let's check is if there is a path that will not call _CFCapitalizeHeader and if we can somehow influence the condition that would avoid the call to _CFCapitalizeHeader. This is quickly checked, especially if you enabled the Separate logical blocks option of otx (-b option for cli).
HTTPMessage::setHeaderFieldValue(__CFString const*, __CFString const*)
+0 000515a4 55 pushl %ebp
+1 000515a5 89e5 movl %esp,%ebp
+3 000515a7 83ec28 subl $0x28,%esp
+6 000515aa 8b4508 movl 0x08(%ebp),%eax
+9 000515ad 8975f8 movl %esi,0xf8(%ebp)
+12 000515b0 8b7510 movl 0x10(%ebp),%esi
+15 000515b3 897dfc movl %edi,0xfc(%ebp)
+18 000515b6 8945f4 movl %eax,0xf4(%ebp)
+21 000515b9 8b450c movl 0x0c(%ebp),%eax
+24 000515bc 890424 movl %eax,(%esp)
+27 000515bf e88a26fbff calll __CFCapitalizeHeader
+32 000515c4 c744240cffffffff movl $0xffffffff,0x0c(%esp)
+40 000515cc 89742408 movl %esi,0x08(%esp)
+44 000515d0 89c7 movl %eax,%edi

We see that there is no path that avoid the call to _CFCapitalizeHeader. So we are left with the last resort solution: patching _CFCapitalizeHeader. With APE Lite, function patching is very easy. You first use APEFindSymbol() to find the address of a non-exported symbol (i.e. __CFCapitalizeHeader), then APEPatchCreate() to replace a function implementation with your own, while still keeping a reference to the original implementation. On iPhone OS, you can use APE Lite+arm (my implementation of the APE Lite API using MobileSubstrate).

The solution

NSMutableURLRequest+CaseSensitive is a category on NSMutableURLRequest that adds these three methods:
   - (void) setAllHTTPHeaderFields:(NSDictionary *)headerFields caseSensitive:(BOOL)caseSensitive;
- (void) setValue:(NSString *)value forHTTPHeaderField:(NSString *)field caseSensitive:(BOOL)caseSensitive;
- (void) addValue:(NSString *)value forHTTPHeaderField:(NSString *)field caseSensitive:(BOOL)caseSensitive;

Just pass caseSensitive:YES for preserving the case of your header fields.

Warning: you SHOULD NOT use this in production code. But hey, HTTP implementations SHOULD be case-insensitive ;-)