BuySellAds.com

Our DNA is written in Objective-C
Jump

Backwards Compatibility if Apple Starts Polishing

You can see that Apple is constantly polishing the APIs from version to version, but sometimes they make a more drastic change that breaks existing code. Well, not exactly “breaks”, but starts to show warnings about you daring to use deprecated methods.

One such change came out of their trying to adhere to their own naming conventions of methods. The second kind of late polishing is if there are new structures introduced without a matching Make macro for easy filling of said structures. I have an example for you, also in CoreLocation.

In this post I’m exploring two such changes and tell you how I dealt with them in a backwards compatible way.

A New Make Function

CoreLocation has a structure CLLocationCoordinate2D which contains latitude and longitude in decimal degrees of any coordinate on Earth. Before 4.0 was introduced I figured that it would be handy to have a CLLocationCoordinate2DMake to go with it, just like you have CGRectMake to create and fill a CGRect.

Turns out, Apple thought so too, and because I guessed the naming convention correctly they introduced this exact method in 4.0 causing my Xcode to complain like this, if I expanded the build results.

In file included from ../DTFloatingIconView.m:9:
../DTFloatingIconView.h:42: error: static declaration of 'CLLocationCoordinate2DMake' follows non-static declaration
/Developer_Stable/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.0.sdk/System/Library/Frameworks/CoreLocation.framework/Headers/CLLocation.h:125: error: previous declaration of 'CLLocationCoordinate2DMake' was here

In the code itself you could only see the first line, but the enlightening detail is found right underneath it. There’s obvious now a “previous declaration” of my helper function. It’s previous to my definition, because it’s in a header and headers precede your code always. And indeed if we have a look at CLLocation.h, there it is. Clearly marked as not available on Mac and available on iOS as of 4.0:

/*
 *  CLLocationCoordinate2DMake:
 *
 *  Discussion:
 *    Returns a new CLLocationCoordinate2D at the given latitude and longitude
 */
CLLocationCoordinate2D CLLocationCoordinate2DMake(CLLocationDegrees latitude, CLLocationDegrees longitude) __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_4_0);

The question in this case was how I could modify my definition such that I could build with 4.0, targeting 3.0 and above and get no warnings, errors or crashes. Here’s how:

#if __IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_4_0
// this method was introduced in SDK 4.0
static inline CLLocationCoordinate2D CLLocationCoordinate2DMake(CLLocationDegrees latitude, CLLocationDegrees longitude)
{
	CLLocationCoordinate2D coord;
	coord.latitude = latitude;
	coord.longitude = longitude;
	return coord;
}
#endif

By using the preprocessor macro __IPHONE_OS_VERSION_MAX_ALLOWED I can check the used SDK version and if it is below 4.0 I define it. Marking it as static inline means that this is built into the binary not as function call, but this code is actually inserted into the calling method as if it were local code.

This way I have a function to satisfy the compiler regardless whether I’m building with SDK 3.x or 4.x. The reason for doing so is that I’m using this in DTAugmentedRealityController where I don’t want to force my customers to build with a specific SDK. So that’s how I implemented it in version 1.1 of the component.

BUT, thinking about it, there’s a problem when building this code with SDK 4.0 which happily compiles it and omits the new function. But what happens if this runs on a 3.x device? A crash with an unrecognized selector. In debugger you’d get something like this:

dyld: lazy symbol binding failed: Symbol not found: _CLLocationCoordinate2DMake
  Referenced from: /var/mobile/Applications/0E8F890C-F78A-4092-99A5-9466AF298D60/test.app/test
  Expected in: /System/Library/Frameworks/CoreLocation.framework/CoreLocation

So how do we make it backwards compatible?

The easiest way I could think of is somewhat brutal, because it involves basically redirecting all calls to my own function. But looking on the bright side, by making it an inline function we actually gain a few nanoseconds by not having to have the function call overhead.

static inline CLLocationCoordinate2D CLLocationCoordinate2DInlineMake(CLLocationDegrees latitude, CLLocationDegrees longitude)
{
	CLLocationCoordinate2D coord;
	coord.latitude = latitude;
	coord.longitude = longitude;
	return coord;
}
 
#define CLLocationCoordinate2DMake CLLocationCoordinate2DInlineMake

This replaces all CLLocationCoordinate2DMake in your code with CLLocationCoordinate2DInlineMake which is an inline function we define regardless of the used SDK or running OS.

It’s a nasty workaround, but at least it does not cause a crash. And we can keep on using the CLLocationCoordinate2DMake function name. That’s some consolidation until we can drop 3.x support later this year.

Renamed Function

In 3.2 the method of CLLocation getDistanceFrom got renamed to distanceFromLocation. You might wonder why. Simple explanation really, Apple reserves the prefix get for methods that are using one or more of their parameters to return a value in addition to the regular return value. This is generally achieved by passing a pointer (= memory address) to the method which the method then can follow and modify the referenced piece of RAM.

Consider for example the method – (void)getValue:(void *)buffer of NSValue where the value of NSValue will be copied into the memory pointed to by “buffer”. There are very few such methods on the higher level APIs. Many more can be found on the Core Foundation level.

The naming convention seems to go that the first part of the method name tells you what is being returned, namely a distance and the last part before the first colon should tell what this parameter actually is. A name that at the same time tells it’s function. So they could have called it distanceThatWillBeCalculatedByAVeryComplicatedFormulaAndSoWeNeedASecondParameterThatIsALocation:, as long as the first and the last item in this camel-cased name. The second rule though is to non unnecessarily prolong method names if it’s possible to get by with 3 or 4 words. A distance from a location, distanceFromLocation. A sorted array by using a selector, sortedArrayUsingSelector.

There are a few such methods in the SDKs that snuck in under the radar of the Naming Convention Adherence Subcommittee at Apple HQ, so they chose to make the switch when introducing 3.2 because at this time that was the first iOS version running on the iPad and thus this change would not cause any problems there. I betcha there was somebody having sleepless nights over this until 3.2 was released in the form of the iPad. Now he can rest in peace.

But to bring balance to the force, somebody being able to rest means that somebody has extra work, we.  Because if we want to keep our code working and warning-free then we have to work around this change.

Since we will be using the latest SDK version for building the new method is valid and the old one is causing a deprecation warning. BUT because we want this code to work on previous iOS versions we cannot exclusively use the distanceFromLocation method, because this would cause an “unrecognized selector” exception (read “crash”) if somebody is running our app on iOS 3.1.x.

Usually you can work around this by using performSelector after having checked that the instance of the receiver responds to it. But in this case there’s a problem: we have a return value that we need to get hold of. I found some workarounds on the Internet that would hack and typecast around. But these methods upset my stomach, so I chose to go for the following method.

- (CLLocationDistance)distanceBetweenLocation:(CLLocation *)location1 andLocation:(CLLocation *)location2
{
	if ([location1 respondsToSelector:@selector(distanceFromLocation:)])
	{
		return [location1 distanceFromLocation:location2];
	}
 
	SEL sel = @selector(getDistanceFrom:);
	if ([location1 respondsToSelector:sel])
	{
		NSMethodSignature *mySignature = [CLLocation instanceMethodSignatureForSelector:sel];
		NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature:mySignature];
		[myInvocation setArgument:&location2 atIndex:2];
		[myInvocation setTarget:location1];
		[myInvocation setSelector:sel];
		[myInvocation invoke];
 
		CLLocationDistance distance;
		[myInvocation getReturnValue:&distance];
 
		return distance;
	}
 
	// should never get here
	return 0;
}

Of course this could have been made as a category extension, but I chose not to because I didn’t want to have to name it distanceBackwardsCompatibleWith3xFromLocation. Reason being, that if you name a category extension method the same as an existing one you overwrite it and there’s no way (similar to super) to call the original method.

Let me explain the above code.

First location1 is asked if it knows distanceFromLocation, if YES, then this is called and we’re done.
Then I’m creating a variable of type SEL to hold the signature (read “selector”) of the pre 3.2 method.
At this point I could assume that this has to be present here, but just to be safe I’m asking again.
Then I’m building an NSInvocation for this signature and setting as target location1.
I’m executing the method by calling invoke.
Then I’m creating a local variable which essentially prompts the compiler to reserve sufficient memory for it.
Finally I’m retrieving the return value of the invocation.

Do you remember what I said about methods prefixed with “get”? That’s another example: it takes a pointer (which we get by the address operator &) to point to the memory it is allowed to modify. Of course it’s your responsibility to have sufficient memory space allocated. We know that this method will return a CLLocationDistance (which is really just a double) I can take the above shown shortcut. Otherwise I’d have to inquire about the size of the return value. Possible, but unnecessary if you know the type.

Conclusion

Regardless if it’s a new convenience function/method or a renaming, as a developer you have to start thinking on several levels at the same time:

1) which methods and functions does my building SDK know about
2) which methods and functions does the executing iOS version know about

Of course you could keep ignoring deprecation warnings, or even go as far as keeping building against an outdated SDK. But your customers expect from you to offer them features that only became available in 4.0. At the same time you still want to get the money from the 3.x crowd.

It boils down to the necessity of having a 3.1 device next to your development Mac just so you can test the backwards compatibility.

So you have to create the compatibility methods, because Apple does not care about backwards compatibility. It’s in their interest that customers update as soon as possible because this decreases the possibility of jailbreaks.

Fun Fact: NSMakeRange still does not comply with Apple’s naming conventions (should be called NSRangeMake instead) and they HATE that. :-)


Categories: Recipes

%d bloggers like this: