Categories in static libraries

Omar Abdelhafith
iOS App Development
4 min readDec 15, 2015

If you are planning to distribute your library as a static lib, you might have come across the issue with using categories in libraries. I had this discussion again with my colleagues @Zendesk which made me decide to write this article about the different ways to deal with categories in a static library.

Linking to a static library

Lets consider an example of a static library that has a category:

// MyStaticLib.h
@interface MyStaticLib : NSObject
+ (void)doSomething;
@end

// MyStaticLib+Category.h
@interface MyStaticLib (Category)
+ (void)sayHello;
@end

When Clang compiles these two files, it generates two different object files; MyStaticLib.o and MyStaticLib+Category.o. These object files is what actually contains the executable code that our client app will use.

As a final step in the static library compilation, all the generated .o files are packaged in a result .a archive file. This archive file is the static library.

When we import the headers for these class in our client app and call [MyStaticLib doSomething] the process of actually linking the compiled code from the library in our client app will be left to the linker.

When the linker links a static lib, it brings only the minimum required objects from the static libs into the final client app. Any object file that is not used won’t be included.

Linking categories issue happen since in Objective C, only class names have symbols. Methods, due to the dynamism of Objc, don’t export symbols. Lets use nm to read the symbols exported from both the object files generated.

> nm -gU MyStaticLib.o
00000000000007a8 S _OBJC_CLASS_$_MyStaticLib
0000000000000780 S _OBJC_METACLASS_$_MyStaticLib

> nm -gU MyStaticLib+Category.o

MyStaticLib.o generated from compiling MyStaticLib.m is exporting an objc class MyStaticLib and its meta class. MyStaticLib+Category.o, on the other hand, is not exporting any symbols.

Since categories only contain methods, they don’t export symbols, that makes it impossible for the linker to decide when to include them in the library and since MyStaticLib+Category.o never gets included into the final app product, calling [MyStaticLib sayHello] will result in an unrecognised selector runtime error.

In order to be able to call category methods, we need to tell the linker to link the category object file. That can be done using any of the bellow solutions;

Solution 1: adding -ObjC to the Other Linker Flags (OTHER_LDFLAGS) in Xcode for the client app. Adding this flag will tell the linker to link all objective c code found in all the linked static libraries.

While this solves the issue, it has two main disadvantages; First, it increases the size of the final binary file, since all the categories, even for classes that we are actually not using, have been added to the binary. Second, it requires to manipulate the client app which is another step that the user of the library has to remember.

Solution 2: In the library project set perform single object prelink GENERATE_MASTER_OBJECT_FILE to YES. This flag will cause the compiler to join all the generated object files into one big object file containing all the symbols and code. When the client app links to the static library, it will include this big object file as a single unit.

You can think of this solution as using -ObjC on the library side. While this might still make your client app larger in size, it does not require an alteration of the build settings on the client app.

Solution 3: Make sure that category code and class code end up in the same object file.

Have the actual code in the same .m file

// MyStaticLib.m
@implementation MyStaticLib @end
@implementation MyStaticLib(Category) @end

Or import the category .m in the class .m file

// MyStaticLib.m
#import "MyStaticLib+Category.m"
@implementation MyStaticLib @end

When compiled, the MyStaticLib.o generated file will contain both the binary code for the class and the category.

While this solution works, it’s scary to import .m files in .m files.

This bring us to my personal favourite solution.

Solution 4: Add a fake symbol in the category code and force this symbol to be loaded in the class file.

We extern a string MyStaticLibCategory in MyStaticLib+Category

// MyStaticLib+Category.h
extern NSString * MyStaticLibCategory;
@interface MyStaticLib (Test)

// MyStaticLib+Category.m
NSString * MyStaticLibCategory;
@implementation MyStaticLib (Category)

And then in MyStaticLib.m use that symbol.

// anywhere in TestLib.m 
__attribute__((used)) static void importCategories () {
id x = MyStaticLibCategory;
}

In the above code, we added a symbol to the category, and then used this symbol in the class implementation. This means, whenever we pull in the code for the class, we also pull the code for the category.

After long discussion, we decided to go with the forth solution to use categories internally. Doing so, we can use categories internally, or externally, without asking the library user to alter his build settings.

As always for notes and followup you can find me here @ifnotrue

--

--

Omar Abdelhafith
iOS App Development

Software developer @Facebook, previously @zendesk. In ❤ with λ, Swift, Elixir and Python. Check my Github https://github.com/nsomar.