Weakly link static library via -weak_library

2020-08-22 07:44发布

问题:

Question:

Is it possible to weakly link a static library (Obj-C)?

Short Details

I do want my custom static framework (MyFramework.framework) to weakly link my other custom static library (libMyLibrary.a).

The functionality behind libMyLibrary.a is optional and can be omitted if there is NO libMyLibrary.a being linked by any 3rd party application that uses MyFramework.framework.

I am using -weak_library. My test application complains that static linker is unable to find MyLibrary's symbol MyClass within MyFramework's ABCTracker.o symbol:

Undefined symbols for architecture arm64:
  "_OBJC_CLASS_$_MyClass", referenced from:
      objc-class-ref in MyFramework(ABCTracker.o)
ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

How to correctly setup weak linking?

Full Details

Setup

  • The Xcode project builds a static Mach-O binary and bundles it into a static framework. The result is MyFramework.framework bundle.
  • Other project builds a static Mach-O binary and the result is a static lib file libMyLibrary.a with a header MyLib.h
  • libMyLibrary.a is removed from MyFramework.framework target's Build Phases > Link Binary With Libraries (as suggested here). Only MyLib.h is available to use library's API from the framework's classes
  • NO Bitcode is used neither in the framework, nor in the library
  • MyFramework.framework, libMyLibrary.a and custom application are all written in Objective-C
  • The MyLib.h defines just one Objective-C class MyClass
  • MyFramework.framework uses MyClass from its own class ABCTracker conditionally checking for symbol availability during runtime, e.g. NSClassFromString(@"MyClass") == NULL
  • From MyFramework target's Build Settings I have set Other Librarian Flags and Other Linker Flags to same value -weak_library MyLibrary:

    OTHER_LDFLAGS = (
        "-weak_library",
        MyLibrary,
    );
    OTHER_LIBTOOLFLAGS = "-weak_library MyLibrary";
    

Result

  • MyFramework.framework builds OK
  • After the build I have checked the symbols in the resulting binary and the output was emty (no symbols from the static library were built into static framework binary):

    $ otool -L MyFramework.framework/MyFramework | grep MyClass
    
  • Despite that, my test application which is not linked with MyLibrary whatsoever, builds with ld error:

    Undefined symbols for architecture arm64:
      "_OBJC_CLASS_$_MyClass", referenced from:
          objc-class-ref in MyFramework(ABCTracker.o)
    ld: symbol(s) not found for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    

What am I doing wrong here?

Other Observations

In MyFramework target I set Other Librarian Flags and Other Linker Flags of to same value:

  • -lMyLibrary. Result: otool shows the library's symbols are built into the framework (expected).
  • -weak-lMyLibrary. Result is the same as for lMyLibrary (is it expected?)

In my application target I set Other Linker Flags to -force_load MyLibrary. Result: the linker error slightly changes:

ld: file not found: MyClass
clang: error: linker command failed with exit code 1 (use -v to see invocation)

回答1:

I have also not been successful at getting XCode to properly weak link a static library, although I have the opposite problem from yours - for me nm showed all the symbols from the static library, and not with "U" (undefined) symbol type as you see when you weak link a framework.

But a workaround you can use is the following:

  1. Create a new Cocoa Touch Framework project called MyWrapper.framework and add libMyLibrary.a to it
  2. Add -ObjC to the linker flags to make sure all the symbols get loaded (and -all_load if you need non Obj-C symbols)
  3. Add your library's headers to the framework's Public Headers section in Build Phases
  4. Build this framework (you'll want to set up an aggregate target to build for all architectures but that is a whole separate topic)
  5. Open your MyFramework.framework project and add MyWrapper.framework to it, weakly linked (i.e. use the toggle to set it to Optional or if you prefer remove it from the Link Binary with Libraries phase and add it via -weak_framework to Other Linker Flags)
  6. Now build MyFramework.framework
  7. In your testing app, remove any reference to libMyLibrary.a
  8. You should be able to run your testing app with no crash and your code should not detect the presence of symbols from libMyLibrary.a
  9. Add MyWrapper.framework to your testing app and then you should see the opposite result - symbols from libMyLibrary.a will be found and usable.