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 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:]


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:@""]];
[request setValue:@"MyValue" forHTTPHeaderField:@"MyField"];
NSLog(@"result: %@", [request allHTTPHeaderFields]);
    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:]

_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 ;-)

Thursday, April 08, 2010

ABGetMe with the iPhone SDK

Unlike the Mac Address Book API, the iPhone Address Book API does not come with the ABGetMe function. That's a pity, but we can do something about it.

Recently, I used MFMailComposeViewController for the first time and I realized that it was displaying my e-mail address. Nicolas Seriot already demonstrated how to retrieve all your e-mail account information in SpyPhone, but I felt there was a more lightweight method for retrieving the e-mail addresses by taking advantage of the MessageUI framework. A class-dump and five minutes later, here is my solution:

Sunday, January 03, 2010

Jasscore pour iPhone

Ma première application iPhone, Jasscore (lien iTunes) est enfin disponible sur l'AppStore au prix de 1.10 CHF. C'est une ardoise virtuelle qui vous permet de compter vos points et ceux de votre adversaire au jass (chibre, mise, etc.)

Les features

  • En français et en allemand
  • Editez le nom des équipes et les scores à atteindre
  • Notez les scores et les annonces
  • Multiplication des scores de 1x à 7x
  • Grand affichage afin que tous les joueurs puissent suivre les scores

Les captures d'écran

Grand Affichage

Je suis ouvert à vos suggestions, alors n'hésitez pas à laisser un commentaire ou à m'envoyer un e-mail.