Linking against a static C++ library in Xcode tends to get complicated. Even though the idea is simple, there are few traps that you can run into.

The idea

  1. Write C++ code.
  2. Write interface which will be usable from Objective-C and Swift.
  3. Package it as a static “.a” library.
  4. Link it to iOS app project.

Let’s start with simple C++ code

We will create a class named ExampleCode which will expose single function helloWorld() that returns static string.

    #ifndef ExampleCode_hpp
    #define ExampleCode_hpp

    #include <stdio.h>

    using namespace std;

    class ExampleCode {
    public:
        const char* helloWorld();
    };

    #endif /* ExampleCode_hpp */

And here is the implementation:

    #include "ExampleCode.hpp"

    const char* ExampleCode::helloWorld() {
        char const *str = "This is my library";
        return str;
    }

Creating interface for Objective-C and Swift

Because Swift still doesn’t have interoperability with C++, we need to utilize Objective-C++ to achieve our goal of using the library from Swift. In the same project for our dummy library, create a new Objective-C file with header:

#import <Foundation/Foundation.h>

@interface NewLibrary : NSObject
- (NSString*)hello;
@end

Now for the implementation file it is important to change its .m extension to .mm because that is what makes it tap into C++ (aka makes it Objective-C++).

#import "NewLibrary.h"
#import "ExampleCode.hpp"

@implementation NewLibrary
- (NSString *)hello {
    ExampleCode* example = new ExampleCode();
    NSString* str = [NSString stringWithUTF8String:example->helloWorld()];
    return str;
}
@end

Also remember ExampleCode.hpp is the C++ header file that I created above, so that is why I import it here.

Packaging this code as static C++ .a library

This is fairly simple stuff, but here are the steps:

  1. Set your scheme to Release configuration.
  2. Build both for “Any iOS Simulator Device (arm64, x86_64)” and for “Any iOS Device (arm64)”. No, you can’t do it both at once.
  3. Find your build products in Xcode’s derived data folder. You should see two folders “Release-iphonesimulator” and “Release-iphoneos”. In there there is your “.a” library file and “include” folder containing “.h” header Objective-C file that we created earlier.

Now, most of the old advice goes to say that you should use lipo command to create universal (fat) binary. However this will only get you in trouble. Firstly, if you try creating universal binary from simulator and iOS device “.a” library files you will get an error telling you that both files are built for arm64 architecture. That is because they actually are since Apple Silicon was introduced. Secondly, don’t try to remove arm64 architecture from simulator “.a” library using lipo -remove arm64 path-to-simulator-lib.a -output library.a, not because it won’t work but because it will create troubles when debugging on simulator later on. Actually, you don’t need to do anything with those files at this point.

Linking against your library in separate iOS project

So in a new iOS project in Xcode it is required to perform a couple of steps to link your newly created static C++ library:

  1. Before you add your “.a” files into iOs project, it would be helpful to rename them such that you can differentiate simulator from real device “.a” libraries because they are different. You can name them something like NewLibrary-sim.a and NewLibrary-device.a.
  2. Add your “.a” files to Xcode project. Make sure that you check the box “Copy files if needed” when dropping “.a” files. Also make sure that you don’t add them to your target because we will link them conditionally in later step.
  3. Add “.h” header file. Just because you have two “.a” files, you don’t need two header files. Also make sure that you check “Copy files if needed” when dropping it into your project.
  4. In project build settings look for “other linker flags” and next to Debug and Release configurations click + icon and add two new entries, one for the simulator SDK and the other for the iOS SDK. In the entry for simulator SDK add path to your simulator .a file, you can write it like this $(SRCROOT)/Libraries/NewLibrary-sim.a. $(SRCROOT) gives you the path to the root of your project. And repeat the same for iOS SDK $(SRCROOT)/Libraries/NewLibrary-device.a.
  5. Now for the library to work, you also need to link against C++ standard library. Fortunately it is pretty straight forward. Go to build phases for your target and add “libc++.tbd” under “Link Binary With Libraries”. This is very important step and one that I see so many other articles fail to mention.
  6. Finally, because interface for our library is written in Objective-C, wee need to create bridging header. You can do that manually or you can add empty Objective-C file to your project and Xcode will offer to create bridging header for you. What ever you choose, just make sure to import your library header in bridging header to make Swift recognize public interface for your library.
//
//  Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "NewLibrary.h"

At this point you should be able to build the app either for simulator or device without any issues.

Things to keep in mind

  • Swift will get support for direct interoperability with C++ very soon. It actually already supports it but the current stable Xcode version still does not ship Swift 5.9. This means that Objective-C won’t be necessary anymore.
  • Don’t fall into traps with removing arm64 from simulator version of your library. Also don’t go into build settings and add arm64 to “Excluded architectures”. If you do so, simulator will utilize Rosetta to run your app and debugging experience gets a lot slower and simulator starts to freeze.

Conclusion

Utilizing language like C++ can be very beneficial as it allows for code sharing between different platforms. However it can get challenging if you are doing it for the first time or you fail to perform one of the steps outlined in this article.