Getting your framework version in run-time

Xcode 13 introduced a new behavior that breaks framework versions: When your clients upload their app, Apple changes the CFBundleShortVersionString of all frameworks to match that of the app.
(See this StackOverflow thread for example)

One way to avoid that is to hard-code your framework version. But if you you want to keep using the MARKETING_VERSION build setting, you can use the following hack.

Create a version variable for your framework, for example:

public class MyFramwork: NSObject {
    @objc static var version: String = ""
}

Then include this Objective-C file in your framework:

#import <MyFramwork/MyFramwork-swift.h>

#define PROCESSOR_STRING(x) PRE_PROCESSOR_STRING_LITERAL(x)
#define PRE_PROCESSOR_STRING_LITERAL(x) @#x

@interface MyFramwork(version)
@property (class) NSString *version; // to access internal property
@end;

@interface MyFramworkLoader: NSObject
@end

@implementation MyFramworkLoader

#ifdef MARKETING_VERSION
+ (void)load {
    dispatch_async(dispatch_get_main_queue(), ^{
        MyFramwork.version = PROCESSOR_STRING(MARKETING_VERSION);
    });
}
#endif

@end

This will assign the MARKETING_VERSION value that you built you framework with, instead of an Apple-manipulated CFBundleShortVersionString value.

4 thoughts on “Getting your framework version in run-time

  1. With a modification to class:
    “`
    public class MyFramework: NSObject {
    @objc public static var version: String = “”
    }
    “`

    and adding pre processor macro MARKETING_VERSION it worked.

    Also you can wrap:

    “`
    #ifdef MARKETING_VERSION
    MyFramwork.version = PROCESSOR_STRING(MARKETING_VERSION);
    #else
    // do smth
    #endif
    “`

  2. Deepa, the code uses Objective-C behaviors that don’t exist in Swift:

    1. `load` is a special class function, that is called when the library loads – even if the client never uses any of the code!

    2. Objective-C can access build settings as pre-processor macros, and with a bit of pre-processor trickery – turn them into usable strings.

    Using these two features, the code executes even if not called, turns the MARKETING_VERSION build setting into a string, and assigns it to MyFramwork.version.

  3. Maciej, thanks for the corrections!

    I fixed the original post, for future readers.

Leave a Reply