Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can one determine the iOS SDK version used to build a binary, programmatically, at run time?

Ah, iOS 8 - lots of unexpected changes from iOS 7 to account for!

tl;dr: Is there a way to programmatically determine the iOS SDK version used to build an app, at run-time (not with a preprocessor macro)?

I'm struggling with some window frame calculations for a library I maintain (distributed as a pre-built static library), as iOS 8 has changed the way the screen coordinate system works.

Two initial observations, running code for iOS 7 with no changes for iOS 8:

  1. When built with the iOS 7 SDK, and run on iOS 8, everything works as prior, no changes necessary.
  2. When built with the iOS 8 SDK, and run on iOS 8, it's broken: some changes in frame calculation are needed to get correct positioning.

So, we change the code, with conditionals on [[UIDevice currentDevice] systemVersion], to work correctly with the new coordinate system. Now:

  1. When built with the iOS 7 SDK, and run on iOS 7, everything works.
  2. When built with the iOS 8 SDK, and run on iOS 8, everything works.

    BUT:

  3. When built with the iOS 7 SDK, and run on iOS 8, the calculations are off - remember, when built with the iOS 7 SDK, everything worked fine prior to the iOS 8-specific code changes. So, the changes made actually broke stuff.

Now, normally, I could happily solve this with some macro conditionals on the SDK version (#ifdef __IPHONE_8_0, etc). But I'm distributing a pre-built static library, built with the iOS 7 SDK, so the code within those conditionals would never make it in. Here's why that's a problem:

If the static library is built with the iOS 7 SDK, but linked into an app built with the iOS 8 SDK, it's the same as if the static library were built with the iOS 8 SDK (because the linking happens at the final app compilation stage, of course). That means I need to have those iOS 8 changes in there, when the app is built with the iOS 8 SDK -- but I can't use a macro conditional to determine whether to use them, as the C preprocessor did its thing during the static library build under iOS 7.

So, my question is this: does anyone know how I might be able to determine whether the app build was made with the iOS 8 SDK, at runtime, from within the pre-compiled static library?

I did try checking for an iOS 8-only SDK feature (-[UIScreen nativeBounds], for example), but that doesn't fly -- the symbol's available regardless of SDK version.

Anyone have any ideas?

like image 868
Michael Tyson Avatar asked Aug 28 '14 03:08

Michael Tyson


People also ask

What is iOS SDK version?

The iOS SDK (iOS Software Development Kit), formerly the iPhone SDK, is a software development kit (SDK) developed by Apple Inc. The kit allows for the development of mobile apps on Apple's iOS and iPadOS operating systems.

What iOS 14 SDK?

The iOS & iPadOS 14 SDK provides support to develop apps for iPhone, iPad, and iPod touch devices running iOS & iPadOS 14. The SDK comes bundled with Xcode 12, available from the Mac App Store.

What is iOS SDK in Swift?

SWIFT Software Development Kit (SDK) provides a simplified way for developers to consume the growing number of API-based services available on the SWIFT platform. The SDK delivers APIs that hide much of lower-level application plumbing, including authentication, authorization, signing and error handling.


2 Answers

Empirical, undocumented observations follow:

Apple records the SDK you built against in the Info.plist under the keys DTSDKBuild and DTSDKName, amongst others. Of those DTSDKName seems to be accessible at runtime and ends with the SDK number. So, getting a:

- (NSString *)buildVersion
{
    // form character set of digits and punctuation
    NSMutableCharacterSet *characterSet = 
        [[NSCharacterSet decimalDigitCharacterSet] mutableCopy];

    [characterSet formUnionWithCharacterSet:
        [NSCharacterSet punctuationCharacterSet]];

    // get only those things in characterSet from the SDK name
    NSString *SDKName = [[NSBundle mainBundle] infoDictionary][@"DTSDKName"];
    NSArray *components =
        [[SDKName componentsSeparatedByCharactersInSet:
              [characterSet invertedSet]]
                  filteredArrayUsingPredicate:
                      [NSPredicate predicateWithFormat:@"length != 0"]];

    if([components count]) return components[0];
    return nil;
}

BOOL wasBuiltWithiOS8SDK = 
    [[[NSBundle mainBundle] infoDictionary][@"DTSDKBuild"] compare:@"11D167"] 
            == NSOrderedDescending;

... with the heavy caveat that I've just reverse engineered that, empirically. So it's technically undocumented API and there's no guarantee whatsoever of robustness in the future.

You could then just use:

BOOL wasBuiltForiOS8 = 
    [[self buildVersion] compare:@"8.0"] != NSOrderedAscending;

(which has the nice feature that it'll evaluate to YES if the version string isn't found, so technically it doesn't matter if Apple takes away DTSDKBuild in the future, it only matters that they don't retroactively remove it from 7.x or somehow one day use a version string that is alphabetically before 8.0)

like image 67
Tommy Avatar answered Sep 17 '22 21:09

Tommy


Here's a provisional solution - hacky, though; it'd be nice to have something more robust.

- (BOOL)hasiOS8ScreenCoordinateBehaviour {
    if ( [[[UIDevice currentDevice] systemVersion] floatValue] < 8.0 ) return NO;

    CGSize screenSize = [[UIScreen mainScreen] bounds].size;
    if ( UIInterfaceOrientationIsLandscape([[UIApplication sharedApplication] statusBarOrientation]) &&
            screenSize.width < screenSize.height ) {
        return NO;
    }

    return YES;
}
like image 26
Michael Tyson Avatar answered Sep 17 '22 21:09

Michael Tyson