That sinking feeling, when the euphoria of ship day evaporates in the face of App Store rejection.
With Apple’s May 1st ban on
-[UIDevice uniqueIdentifier], such buzzkills are easier to stumble upon than ever. Maybe you’ve been preparing yourself though, and you’ve identified all the spots in your app where you were using
uniqueIdentifier, and have replaced it with many of the great options around. But are you really ready to ship?
Not necessarily – did you check your favorite third-party libraries? Maybe you’re pulling the source in from GitHub as a submodule in your project, in which case it’s easy enough to search the code and verify. But what about that great, closed-source library you paid good money for? Maybe they’ve updated but you haven’t been able to justify the upgrade price, or you need to support a less recent version of iOS than the bleeding edge targeted by by the newer releases of the library. How are you going to clear those?
You may be thinking, “No problem, I’ll just
grep -R my project and be done with it!”. But
strings cast a wide net, and you’ll find yourself with a number of false positives. As tempting as it is to search your whole project, you should start with the executable binary inside your app’s bundle – and for the most accurate check, you’ll want to do this on a Release build.
By way of demonstration, I’ve created an app “SuperApp” which uses “LibAwesome”, a static library for which we’ll pretend we don’t have the source, as well as the Facebook SDK. Open Xcode, and do a Release build for an iOS device. Then go to the Organizer, choose the Projects segment, select your app’s project in the left side bar, and copy the derived data path. Open Terminal, and
cd to the path you just copied. Then type
cd Build/Products/Release-iphoneos/SuperApp.app (replace your app’s name as needed) and you should be in the directory which will contain an executable file with the same name as your app bundle.
At this point, we’re going to check for explicit method calls to
uniqueIdentifier as well as instances of
@selector(uniqueIdentifier). We can do both at the same time by using the
otool command (man page) to display the contents of the text segment of the binary, and search it for the string
otool -v -s __TEXT __objc_methname SuperApp | grep uniqueIdentifier
If you see nothing at all as an output to this command, then you’re most likely in the clear! However, if you see any output that looks like:
00039a06 uniqueIdentifier 00039a0a uniqueIdentifier
then your app includes code that’s possibly either
[[UIDevice currentDevice] uniqueIdentifier] or
[[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)], and it’s going to be rejected during the App Store submission process.
Our next step is to find out what libraries are actually causing the problem. I mentioned earlier that relying on
grep can cause false positives, but as long as we’re aware of what the false positives look like, we can still use them to identify the real culprits. I wrote the following script so as to both identify files that reference the proscribed method, but also, by way of the
--context flag to
grep, give enough information that we can judge which libraries will actually present an issue at validation time. Run this script from your iOS project’s root folder, and review the output.
#!/bin/bash for match in $(grep -lR uniqueIdentifier *); do printf "File: %s\n****************************************\n\n" "$match" strings $match | grep --context=15 uniqueIdentifier printf "\n\n\n" done
When I run the above script in the SuperApp project, I find a number of matching files, many in the Facebook SDK. I also get some matches in LibAwesome (the contrived closed-source library which contains calls to
uniqueIdentifier). All the Facebook SDK calls follow a specific pattern which actually turns out to be a false positivie, and can be safely ignored! The pattern looks like this:
File: SuperApp/FacebookSDK.framework/Versions/Current/FacebookSDK **************************************** accountStore accountTypeFB device name setName: model setModel: localizedModel setLocalizedModel: systemName setSystemName: systemVersion setSystemVersion: orientation setOrientation: uniqueIdentifier setUniqueIdentifier: identifierForVendor NSUUID setIdentifierForVendor: generatesDeviceOrientationNotifications setGeneratesDeviceOrientationNotifications: batteryMonitoringEnabled batteryState setBatteryState: batteryLevel setBatteryLevel: proximityMonitoringEnabled proximityState setProximityState: multitaskingSupported -- --
(The content underneath the asterisks will be repeated thrice for each file – once per architecture the framework was built for.)
Once again, if you see this pattern, it is harmless. It seems that in certain circumstances I’ve yet to identify precisely, sending any message to
UIDevice in a source file causes the object file compiled from it to include that string pattern – but linking to the frameworks does not cause that string to be included in an app’s binary, or anywhere else outside the .o and .a files. This means you’re safe from App Store rejection if you see this pattern. The following (better) command, prints only blank lines for files that would have triggered a false positive, and will let you narrow down the problematic files quicker. Run it from Terminal, again in your Xcode project’s root:
for match in $(grep -lR uniqueIdentifier *); do printf "File:%s\n\n" $match; otool -v -s __TEXT __objc_methname $match | grep uniqueIdentifier; printf "\n\n"; done;
You can get away with only running this, but I find it’s still nice to use the variant with
strings as a sanity check. The output from this command will look like this:
File:SuperApp/FacebookSDK.framework/FacebookSDK File:SuperApp/FacebookSDK.framework/Versions/A/FacebookSDK File:SuperApp/FacebookSDK.framework/Versions/Current/FacebookSDK File:SuperApp/Lib/libLibAwesome.a 00000552 uniqueIdentifier 00000552 uniqueIdentifier 0000052a uniqueIdentifier
This output indicates that
libLibAwesome.a is calling
uniqueIdentifier, likely only in one place in the code (listed thrice for i386, ARMv7, ARMv7s). The file names with only blank lines in them have the string inside them, but not as part of any code that will end up in your app’s binary.
Having found that LibAwesome contains the banned method, it’s time to get an updated version from the vendor, integrate it, and ship it!
(Note: the following should probably not be attempted by anyone.) If for whatever reason an updated version of your library is unavilable, or the version is incompatible with your app, then there is a plan B. We can use a hex editor such as the fantastic Hex Fiend to replace
uniqueIdentifier occurrences in the __TEXT segment of the library with a new method name that shouldn’t trip Apple’s detection. The method name needs to be exactly sixteen characters long, and will also need need to be implemented in a
UIDevice category. That implementation should return some type of App Store-legal UUID. There are a lot of scary implications in doing this, and it should really be a last resort, but with careful testing and experimenting one can potentially get a stable build that will pass App Store validation – even in the absence of an official upgrade to a needed closed-source library.
To sum up, by knowing where to look and using a few simple command line tools like
otool, you can validate your own app for violations of Apple’s new policy before you get rejected. This will save you time and heartache, and keep your release cycle humming along!
Carl was a Software Engineer for Double Encore, Inc., a leading mobile development company, where he has been a core team member on apps with millions of downloads. He is also a graduate student at the University of Colorado at Boulder. When not at work, school, or spending time with his family, he actively contributes to Stack Overflow.