Skip to content

Instantly share code, notes, and snippets.

@steipete
Last active March 12, 2024 13:57
Show Gist options
  • Save steipete/6526860 to your computer and use it in GitHub Desktop.
Save steipete/6526860 to your computer and use it in GitHub Desktop.
A simple way to detect at runtime if we're running in UIKit legacy mode or the new "flat" variant. Written for our PDF iOS SDK (http://pspdfkit.com), where the precompiled binary needs to detect at runtime in what variant it's running. Want more stuff like that? Follow me on Twitter: http://twitter.com/steipete
// Taken from http://PSPDFKit.com. This snippet is under public domain.
#define UIKitVersionNumber_iOS_7_0 0xB57
BOOL PSPDFIsUIKitFlatMode(void) {
static BOOL isUIKitFlatMode = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// We get the modern UIKit if system is running >= iOS 7 and we were linked with >= SDK 7.
if (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) {
isUIKitFlatMode = (NSVersionOfLinkTimeLibrary("UIKit") >> 16) >= UIKitVersionNumber_iOS_7_0;
}
});
return isUIKitFlatMode;
}
@steipete
Copy link
Author

If kCFCoreFoundationVersionNumber_iOS_7_0 is undefined, use following snippet:

ifndef kCFCoreFoundationVersionNumber_iOS_7_0

define kCFCoreFoundationVersionNumber_iOS_7_0 847.2

endif

@khakionion
Copy link

Is there a downside to just making a new UIWindow in all cases? It's not that much overhead is it?

@jnjosh
Copy link

jnjosh commented Sep 15, 2013

Would it be simpler to use something like this?

isUIKitFlatMode = [UIWindow instancesRespondToSelector:@selector(tintColor)];

Then you don't have to worry about checking the key window or using the temporary window.

@steipete
Copy link
Author

@jnjosh UIWindow will always respond to tintColor on iOS 7, even in legacy mode.

@khakionion This is only a fallback and only created if we don't yet have a window, and instantly destroyed afterwards. Wasn't an issue in my test, and no performance penalty either. But it's not pretty, I agree.

@nzhuk
Copy link

nzhuk commented Oct 30, 2013

How about [[NSBundle mainBundle] objectForInfoDictionaryKey:@"DTSDKName"] ? Xcode injects that key into app's Info.plist during compilation, and it contains the name of Base SDK (e.g. "iphoneos6.1" or "iphonesimulator6.1").

@steipete
Copy link
Author

Looks like this doesn't work anymore as of iOS 7.1b2. I've updated the code for a new way that works.

@steipete
Copy link
Author

NSVersionOfLinkTimeLibrary seems to be the most stable solution. Are those version numbers defined somewhere, or should I just use them as magic constants?

@danieleggert
Copy link

Check the dyld(3) man page:

int32_t
NSVersionOfLinkTimeLibrary(const char* libraryName);

NSVersionOfLinkTimeLibrary() returns the current_version number that the main executable was linked
against at build time.  The libraryName parameter would be "bar" for /path/libbar.3.dylib and "Foo" for
/path/Foo.framework/Versions/A/Foo.  This function returns -1 if the main executable did not link
against the specified library.

They're just whatever number the team for that library uses.

@0xced
Copy link

0xced commented Dec 15, 2013

In order to check which SDK version was used to build a binary, UIKit uses the _UIApplicationLinkedOnOrAfter function. It is implemented by comparing the major version of UIKit at link time (NSVersionOfLinkTimeLibrary("UIKit") >> 16) to a value in the __UIKitVersionNumbers table.

Here is this UIKit version numbers table, built by running otool -L on UIKit of all these iOS versions (yes I have a huge archive!):

IndexiOS VersionHexadecimalDecimal
02.00x0E5229
12.10x2EB747
22.20x2F0752
32.2.10x2F1753
43.00x333819
53.10x3E81000
63.20x44C1100
74.00x4B01200
84.10x5141300
94.2.10x5781400
104.2.60x5821410
114.30x5DC1500
125.00x6401600
135.10x6A41700
146.00x9442372
156.10x94C2380
167.00xB572903
177.10xB772935

Weirdly enough, the __UIKitVersionNumbers contains 0x2EC instead of 0x2EB for iPhone OS 2.1. But since nobody cares at all about iPhone OS 2 this should not be a problem.

@nacho4d
Copy link

nacho4d commented Dec 16, 2013

Awesome thread!

One tiny thing to add:

If kCFCoreFoundationVersionNumber_iOS_7_0 is undefined, use following snippet:

ifndef kCFCoreFoundationVersionNumber_iOS_7_0

define kCFCoreFoundationVersionNumber_iOS_7_0 847.2

endif

Instead of the defining the var I would use the & ... like

if (&kCFCoreFoundationVersionNumber_iOS_7_0 &&
    kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_0) 
{ ...

@bgglenn
Copy link

bgglenn commented Oct 2, 2014

For anyone googling their way here, 8.0 is 0xCF6.

@steipete
Copy link
Author

steipete commented Aug 5, 2016

iOS 9.3: 0xDB8
iOS 10b4: 0xE0C

@steipete
Copy link
Author

steipete commented Oct 1, 2018

For iOS 12 (GM), it seems that casting to unsigned integer is now required:

(lldb) p (int32_t)NSVersionOfLinkTimeLibrary("UIKit") >> 16
(int32_t) $4 = -4536
(lldb) p (uint32_t)NSVersionOfLinkTimeLibrary("UIKit") >> 16
(uint32_t) $5 = 61000

-> UIKitVersionNumber_iOS_12_0 = 0xEE48

@clementbarry
Copy link

clementbarry commented Sep 30, 2021

iOS 15.0 is 0x13CB

(lldb) p (uint32_t)NSVersionOfLinkTimeLibrary("UIKit") >> 16
(uint32_t) $2 = 5067

@clementbarry
Copy link

iOS 15.2 is 0x1455


(lldb) p (uint32_t)NSVersionOfLinkTimeLibrary("UIKit") >> 16
(uint32_t) $1 = 5205

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment