Linking Objective-C Categories in a Static Library

linking objective-c categories in a static library

Check out Building Objective-C static libraries with categories:

Objective-C does not define linker symbols for each function (or
method, in Objective-C) - instead, linker symbols are only generated
for each class. If you extend a pre-existing class with categories,
the linker does not know to associate the object code of the core
class implementation and the category implementation. This prevents
objects created in the resulting application from responding to a
selector that is defined in the category.

To resolve this issue, the target linking against the static library
must pass the -ObjC option to the linker.
This flag causes the linker
to load every object file in the library that defines an Objective-C
class or category. While this option will typically result in a larger
executable (due to additional object code loaded into the
application), it will allow the successful creation of effective
Objective-C static libraries that contain categories on existing
classes.


Important: For 64-bit and iPhone OS applications, there is a
linker bug that prevents -ObjC from loading objects files from static
libraries that contain only categories and no classes. The workaround
is to use the -all_load or -force_load flags.

Source: @albertamg (linking objective-c categories in a static library)

Objective-C categories in static library

Solution: As of Xcode 4.2, you only need to go to the application that is linking against the library (not the library itself) and click the project in the Project Navigator, click your app's target, then build settings, then search for "Other Linker Flags", click the + button, and add '-ObjC'. '-all_load' and '-force_load' are no longer needed.

Details:
I found some answers on various forums, blogs and apple docs. Now I try make short summary of my searches and experiments.

Problem was caused by (citation from apple Technical Q&A QA1490 https://developer.apple.com/library/content/qa/qa1490/_index.html):

Objective-C does not define linker
symbols for each function (or method,
in Objective-C) - instead, linker
symbols are only generated for each
class. If you extend a pre-existing
class with categories, the linker does
not know to associate the object code
of the core class implementation and
the category implementation. This
prevents objects created in the
resulting application from responding
to a selector that is defined in the
category.

And their solution:

To resolve this issue, the static
library should pass the -ObjC option
to the linker. This flag causes the
linker to load every object file in
the library that defines an
Objective-C class or category. While
this option will typically result in a
larger executable (due to additional
object code loaded into the
application), it will allow the
successful creation of effective
Objective-C static libraries that
contain categories on existing
classes.

and there is also recommendation in iPhone Development FAQ:

How do I link all the Objective-C
classes in a static library? Set the
Other Linker Flags build setting to
-ObjC.

and flags descriptions:

-all_load Loads all members of static archive libraries.

-ObjC Loads all members of static archive libraries that implement an
Objective-C class or category.

-force_load (path_to_archive) Loads all members of the specified static
archive library. Note: -all_load
forces all members of all archives to
be loaded. This option allows you to
target a specific archive.

*we can use force_load to reduce app binary size and to avoid conflicts which all_load can cause in some cases.

Yes, it works with *.a files added to the project.
Yet I had troubles with lib project added as direct dependency. But later I found that it was my fault - direct dependency project possibly was not added properly. When I remove it and add again with steps:

  1. Drag&drop lib project file in app project (or add it with Project->Add to project…).
  2. Click on arrow at lib project icon - mylib.a file name shown, drag this mylib.a file and drop it into Target -> Link Binary With Library group.
  3. Open target info in fist page (General) and add my lib to dependencies list

after that all works OK. "-ObjC" flag was enough in my case.

I also was interested with idea from http://iphonedevelopmentexperiences.blogspot.com/2010/03/categories-in-static-library.html blog. Author say he can use category from lib without setting -all_load or -ObjC flag. He just add to category h/m files empty dummy class interface/implementation to force linker use this file. And yes, this trick do the job.

But author also said he even not instantiated dummy object. Mm… As I've found we should explicitly call some "real" code from category file. So at least class function should be called.
And we even need not dummy class. Single c function do the same.

So if we write lib files as:

// mylib.h
void useMyLib();

@interface NSObject (Logger)
-(void)logSelf;
@end

// mylib.m
void useMyLib(){
NSLog(@"do nothing, just for make mylib linked");
}

@implementation NSObject (Logger)
-(void)logSelf{
NSLog(@"self is:%@", [self description]);
}
@end

and if we call useMyLib(); anywhere in App project
then in any class we can use logSelf category method;

[self logSelf];

And more blogs on theme:

http://t-machine.org/index.php/2009/10/13/how-to-make-an-iphone-static-library-part-1/

http://blog.costan.us/2009/12/fat-iphone-static-libraries-device-and.html

ObjC: How to compile static library that includes optional classes that depend on a third party library

The best practice is always to have the final binary link all the static libs required. You should never bundle one static library into another. You should absolutely never bundle a well-known (i.e. open-source) static library into a static library you ship. This can create incredible headaches for the final consumer because they can wind up with multiple versions of the same code. Tracking down the bugs that can come from this is insanely difficult. If they're lucky, they'll just get confusing compiler errors. If they're unlucky, their code will behave in unpredictable ways and randomly crash.

Ship all the static libraries separately. Tell your clients which ones they need to link for various configurations. Trying to avoid this just makes their lives difficult.

Some other discussions that may be useful:

  • Duplicate Symbol Error: SBJsonParser.o? (Example of a customer who ran into a vendor doing this to him)
  • Linking static libraries, that share another static library
  • Why don't iOS framework dependencies need to be explicitly linked to a static library project or framework project when they do for an app project?

The -ObjC flag should be preventing the automatic stripping of ClassA entirely, whether its used or not (see TN1490 for more details).

If ClassA is never used except in certain circumstances and you want to save space, you should probably move ClassA into its own static library. Or use #ifdef to conditionally compile it.

Alternately, you can remove the -ObjC flag and use -force_load to individually load any category-only compile units (which is the problem -ObjC is used to address).

How to weak import a class from one static library into another library (Objective-C)

From glandium.org:

Add -undefined dynamic_lookup to Other linker flags.

Can the -ObjC flag be applied selectively to static libraries?

OK so the answer is to use -force_load instead of -ObjC as -force_load is more focused.

So WRT to the Google Maps SDK, if you followed the instructions and copied the static framework into the app project directory, then the framework will be in the project root directory and you can remove -ObjC from the Other Linker Flags and replace it with

-force_load GoogleMaps.framework/Versions/Current/GoogleMaps:

Sample Image

Nothing else needs changing.

For other libraries you will need to use the full static library path as the argument to -force_load.

iOS static library does not work

Did your library contain any categories?

If so, I think you should follow these steps to get it work:

  1. In Xcode, double-click the target's name under "Targets" in the Project window.
  2. Choose the Build Settings panel.
  3. Scroll down to the "Other Linker Flags" build setting under the Linking collection and set its value to "-all_load -ObjC".

Please refer to corresponding Apple Documentation

Here is a related question: Objective-C categories in static library



Related Topics



Leave a reply



Submit