Objective-C categories in static library

时间:2021-02-24 16:28:55

ASK:

Can you guide me how to properly link static library to iphone project. I use staic library project added to app project as direct dependency (target -> general -> direct dependecies) and all works OK, but categories. A category defined in static library is not working in app.

So my question is how to add static library with some categories into other project?

And in general, what is best practice to use in app project code from other projects?

ANSWER 1:

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 http://developer.apple.com/mac/library/qa/qa2006/qa1490.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 wich 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 projecct 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

ANSWER 2:

The answer from Vladimir is actually pretty good, however, I'd like to give some more background knowledge here. Maybe one day somebody finds my reply and may find it helpful.

The compiler transforms source files (.c, .cc, .cpp, .m) into object files (.o). There is one object file per source file. Object files contain symbols, code and data. Object files are not usable directly by the operating system.

Now when building a dynamic library (.dylib), a framework, a loadable bundle (.bundle) or an executable binary, these object files are linked together by the linker to produce something the operating system considers "usable", e.g. something it can directly load to a specific memory address.

However when building a static library, all these object files are simply added to a big archive file, hence the extension of static libraries (.a for archive). So an .a file is nothing than an archive of object (.o) files. Think of a TAR archive or a ZIP archive without compression. It's just easier to copy a single .a file around than a whole bunch of .o files (similar to Java, where you pack .class files into a .jar archive for easy distribution).

When linking a binary to a static library (= archive), the linker will get a table of all symbols in the archive and check which of these symbols are referenced by the binaries. Only the object files containing referenced symbols are actually loaded by the linker and are considered by the linking process. E.g. if your archive has 50 object files, but only 20 contain symbols used by the binary, only those 20 are loaded by the linker, the other 30 are entirely ignored in the linking process.

This works quite well for C and C++ code, as these languages try to do as much as possible at compile time (though C++ also has some runtime-only features). Obj-C, however, is a different kind of language. Obj-C heavily depends on runtime features and many Obj-C features are actually runtime-only features. Obj-C classes actually have symbols comparable to C functions or global C variables (at least in current Obj-C runtime). A linker can see if a class is referenced or not, so it can determine a class being in use or not. If you use a class from an object file in a static library, this object file will be loaded by the linker because the linker sees a symbol being in use. Categories are a runtime-only feature, categories aren't symbols like classes or functions and that also means a linker cannot determine if a category is in use or not.

If the linker loads an object file containing Obj-C code, all Obj-C parts of it are always part of the linking stage. So if an object file containing categories is loaded because any symbol from it is considered "in use" (be it a class, be it a function, be it a global variable), the categories are loaded as well and will be available at runtime. Yet if the object file itself is not loaded, the categories in it will not be available at runtime. An object file containing only categories is never loaded because it contains no symbols the linker would ever consider "in use". And this is the whole problem here.

Several solutions have been proposed and now that you know how all this plays together, let's have another look on the proposed solution:

  1. One solution is to add -all_load to the linker call. What will that linker flag actually do? Actually it tells the linker the following "Load all object files of all archives regardless if you see any symbol in use or not'. Of course, that will work; but it may also produce rather big binaries.

  2. Another solution is to add -force_load to the linker call including the path to the archive. This flag works exactly like -all_load, but only for the specified archive. Of course this will work as well.

  3. The most popular solution is to add -ObjC to the linker call. What will that linker flag actually do? This flag tells the linker "Load all object files from all archives if you see that they contain any Obj-C code". And "any Obj-C code" includes categories. This will work as well and it will not force loading of object files containing no Obj-C code (these are still only loaded on demand).

  4. Another solution is the rather new Xcode build setting Perform Single-Object Prelink. What will this setting do? If enabled, all the object files (remember, there is one per source file) are merged together into a single object file (that is not real linking, hence the name PreLink) and this single object file (sometimes also called a "master object file") is then added to the archive. If now any symbol of the master object file is considered in use, the whole master object file is considered in use and thus all Objective-C parts of it are always loaded. And since classes are normal symbols, it's enough to use a single class from such a static library to also get all the categories.

  5. The final solution is the trick Vladimir added at the very end of his answer. Place a "fake symbol" into any source file declaring only categories. If you want to use any of the categories at runtime, make sure you somehow reference the fake symbol at compile time, as this causes the object file to be loaded by the linker and thus also all Obj-C code in it. E.g. it could be a function with an empty function body (which will do nothing when being called) or it could be a global variable accessed (e.g. a global int once read or once written, this is sufficient). Unlike all other solutions above, this solution shifts control about which categories are available at runtime to the compiled code (if it wants them to be linked and available, it accesses the symbol, otherwise it doesn't access the symbol and the linker will ignore it).

That's all folks.

Oh, wait, there's one more thing:

The linker has an option named -dead_strip. What does this
option do? If the linker decided to load an object file, all symbols of
the object file become part of the linked binary, whether they are used
or not. E.g. an object file contains 100 functions, but only one of them
is used by the binary, all 100 functions are still added to the binary
because object files are either added as a whole or they are not added
at all. Adding an object file partially is usually not supported by
linkers.

However, if you tell the linker to "dead strip", the linker will
first add all the object files to the binary, resolve all the references
and finally scan the binary for symbols not in use (or only in use by
other symbols not in use). All the symbols found to be not in use are
then removed as part of the optimization stage. In the example above,
the 99 unused functions are removed again. This is very useful if you
use options like -load_all, -force_load or Perform Single-Object Prelink
because these options can easily blow up binary sizes dramatically in
some cases and the dead stripping will remove unused code and data
again.

Dead stripping works very well for C code (e.g. unused functions,
variables and constants are removed as expected) and it also works quite
good for C++ (e.g. unused classes are removed). It is not perfect, in
some cases some symbols are not removed even though it would be okay to
remove them, but in most cases it works quite well for these languages.

What about Obj-C? Forget about it! There is no dead stripping for
Obj-C. As Obj-C is a runtime-feature language, the compiler cannot say
at compile time whether a symbol is really in use or not. E.g. an Obj-C
class is not in use if there is no code directly referencing it,
correct? Wrong! You can dynamically build a string containing a class
name, request a class pointer for that name and dynamically allocate the
class. E.g. instead of

MyCoolClass * mcc = [[MyCoolClass alloc] init];

I would also write

NSString * cname = @"CoolClass";
NSString * cnameFull = [NSString stringWithFormat:@"My%@", cname];
Class mmcClass = NSClassFromString(cnameFull);
id mmc = [[mmcClass alloc] init];

In both cases mmc is a reference to an object of the class "MyCoolClass", but there is no direct reference to this class in the second code sample (not even the class name as a static string). Everything happens only at runtime. And that's even though classes are actually real symbols. It's even worse for categories, as they are not even real symbols.

So if you have a static library with hundreds of objects, yet most of your binaries only need a few of them, you may prefer not to use the solutions (1) to (4) above. Otherwise you end up with very big binaries containing all these classes, even though most of them are never used. For classes you usually don't need any special solution at all since classes have real symbols and as long as you reference them directly (not as in the second code sample), the linker will identify their usage pretty well on its own. For categories, though, consider solution (5), as it makes it possible to only include the categories you really need.

E.g. if you want a category for NSData, e.g. adding a compression/decompression method to it, you'd create a header file:

// NSData+Compress.h
@interface NSData (Compression)
- (NSData *)compressedData;
- (NSData *)decompressedData;
@end void import_NSData_Compression ( );

and an implementation file

// NSData+Compress
@implementation NSData (Compression)
- (NSData *)compressedData
{
// ... magic ...
} - (NSData *)decompressedData
{
// ... magic ...
}
@end void import_NSData_Compression ( ) { }

Now just make sure that anywhere in your code import_NSData_Compression() is called. It doesn't matter where it is called or how often it is called. Actually it doesn't really have to be called at all, it's enough if the linker thinks so. E.g. you could put the following code anywhere in your project:

__attribute__((used)) static void importCategories ()
{
import_NSData_Compression();
// add more import calls here
}

You don't have to ever call importCategories() in your code, the attribute will make the compiler and linker believe that it is called, even in case it is not.

And a final tip:

If you add -whyload to the final link call, the linker will
print in the build log which object file from which library it did load
because of which symbol in use. It will only print the first symbol
considered in use, but that is not necessarily the only symbol in use of
that object file.