16

I developed a Cocoa touch framework and am having problems with third party static framework classes which are embedded inside of it.

The problem is symbol collisions when consumer projects use my framework and also import the third party static framework which my framework uses.

I eventually want to remove these classes from my framework since they are conflicting with host project classes (they use same third party framework) and somehow tell my framework to rely on main project third party framework (I will instruct devs to import the framework), Or alternatively I will add a prefix to these classes so that when hosting projects embed my framework and use the same third party framework as my own framework it won't get a symbol collisions

Any help or direction will be welcomed!

Krumelur
  • 31,081
  • 7
  • 77
  • 119
Michael A
  • 5,770
  • 16
  • 75
  • 127
  • `otool` is likely to be able to show you the symbols; I'm not sure about changing them. Hence my failure to provide an answer. – Tommy Aug 10 '15 at 13:48
  • @Tommy (I updated the question) thanks, do you know any other approach to solving that symbol conflicts issue? – Michael A Aug 10 '15 at 13:53
  • If your project require external frameworks there are two ways to solve it - use a package manager to handle dependencies (e.g. CocoaPods) or let the user download the dependencies separately (basically the user of your framework will have to do the linking and make sure nothing is linked twice). – Sulthan Aug 10 '15 at 19:13
  • @Sulthan about instructing the user to download the dependencies its fine with me, but the problem is that my framework wont build if it don't have the required framework linked (it fail saying module x.framework is missing) – Michael A Aug 11 '15 at 06:36

4 Answers4

8

CocoaPods can help you to resolve the problem with duplicate symbols.
Below I provided detailed explanations for how to make it happen:

Definitions
Let's make some definitions for simpler explanations:
MyFramework - framework that you are developing.
MyApplication - application that uses MyFramework.
OtherFramework - third-party framework that is used in MyFramework and MyApplication.

Problem
As I understand the problem is that Xcode fails to build with "duplicated symbols" error in OtherFramework.

Solution
Here are conditions that you need satisfy to fix that problem:

1) MyFramework has to refer to OtherFramework by CocoaPods:

// MyFramework Podfile
use_frameworks!
pod "OtherFramework"

2) MyApplication has to refer to OtherFramework by CocoaPods:

// MyApplication Podfile
use_frameworks!
pod "OtherFramework"

3) MyApplication can use any mechanism to refer to MyFramework (by CocoaPods or by Drag&Drop framework to project).

4) OtherFramework has to be built with CocoaPods.
If it's not built with CocoaPods yet, you can make it yourself.
For this goal you need to create OtherFramework.podspec and optionally submit it to CocoaPods private repository. It doesn't matter if you have source files or just OtherFramework.framework bundle. More details for building CocoaPod here.

Vlad Papko
  • 13,184
  • 4
  • 41
  • 57
  • While your solution might work the problem for me is that i cant force the user to use cocoa pods to refer to the "otherFramework". i can tell him to drag and drop but not force CocoaPods. i am developing an SDK and want the users to use it easily as possible. What do you mean in your "4" built with CocoaPods? you mean already exist as a pod? I am taking about the GoogleMobileAds as the "otherFramework" – Michael A Aug 11 '15 at 06:51
  • @MichaelA, You know what, I just verified and 2nd point isn't required. So `MyApplication` can refer to `OtherFramework` by any mechanism. So you don't need force your customers to use CocoaPods for `GoogleMobileAds`. About 4th point, yes, your framework `MyFramework` has to refer to `GoogleMobileAds` by CocoaPods. I verified, it's built with CocoaPods. It's available here: https://cocoapods.org/pods/GoogleMobileAds. Let me know if it's not clear what I mean. – Vlad Papko Aug 11 '15 at 07:29
  • @MichaelA, So in my answer the most important point is 1st: MyFramework has to refer to OtherFramework by CocoaPods. – Vlad Papko Aug 11 '15 at 07:30
  • thanks. i tried it (refer to "OtherFramework" by CocoaPods in MyFramework) but it gives duplicate symbols errors at runtime. Notice that it will give the errors only if MyApplication is actually using classes of the "OtherFramework" if its just linking it without using it no errors will show – Michael A Aug 11 '15 at 07:44
  • @MichaelA, it's strange. Do you have some example project where this issue is reproducible? Because in my case it works fine, I also use functions of `OtherFramework` in both `MyFramework` and `MyApplication`. – Vlad Papko Aug 11 '15 at 07:53
  • just tried it again with same problem: objc[1480]: Class GADGestureUtil is implemented in both /private/var/mobile/Containers/Bundle/Application/9759494A-50B8-4896-BFD0-E178F396621B/VeediDemoApp.app/Frameworks/VeediFramework.framework/VeediFramework and /private/var/mobile/Containers/Bundle/Application/9759494A-50B8-4896-BFD0-E178F396621B/VeediDemoApp.app/VeediDemoApp. One of the two will be used. Which one is undefined. How do you connect the "MyFramework" and "OtherFramework" to "MyProject" ? with simple drag and drop? – Michael A Aug 11 '15 at 08:38
  • I also tried refer "OtherFramework" from "MyProject" and "MyFramework" using CocoaPods still same error :(. I appreciate the help by the way – Michael A Aug 11 '15 at 08:59
  • Not sure how this is supposed to work assuming MyFramework is integrated into MyProject not as .framework but as .xcodeproj file (since for CocoaPods we need to use workspace) – Aleksey Mazurenko Feb 12 '19 at 12:59
7

TL;DR

Using dynamic frameworks, you should not have to care much about it, as the linker uses a sensible default behaviour. If you really want to do what you ask, you could instruct the linker to do so, at the risk of failure at run-time. See towards the end of the answer for an explanation.

In general, regarding static linking

This is another version of the classic "dependency hell" problem. Theoretically, there are two solutions when linking object files statically:

  1. State your dependency and let the user of your framework solve it in their build system. Some ideas:

    • External systems such as CocoaPods and Carthage will help you at the downside of forcing constraints onto your user's build system (i.e. the user might not use CocoaPods).

    • Include the dependency and the header files for it, instructing your users not use your provided version of that dependency. The downside is of course that your user cannot switch the implementation should they need to.

    • (Maybe the easiest). Build two versions of your framework, one with the dependency library not linked in. The user can then choose wheter to use your provided version or their own. The downside is that there is no good way of determining if their version will be compatible with your code.

  2. Avoid leaking your dependency and encapsulate it within your framework, at the expense of larger code size. This is usually my path of preference these days now that code size is not a real problem even on mobile devices. Methods include:

    • Include the dependency as source files in your project. Use #define macros to rename the symbols (or using only static symbols).
    • Building your dependency from source, renaming the symbols in a code pre-build (or one off) step.
    • Building everything as you were, then renaming the "internal" symbols in the built library. This could potentially cause bugs, especially since swift/obj-c have dynamic aspects. See this question, for example.

This post discusses some of the pros and cons of the different alternatives.

When using dynamic frameworks

If you are building a dynamic framework, best practice is to do "2." above. Specifically, linking dynamically will prevent the duplicate symbol problem, as your library can link to its version of the third party library regardless of the library any client uses.

Here is a simple example (using C for simplicity, but should apply to Swift, ObjC, C++ or anything linked using ld):

Note also that I assume your third party library is written in C/objC/C++, since Swift classes can (AFAIK) not live in static libraries.

myapp.c

#include <stdio.h>

void main() {
  printf("Hello from app\n");

  third_party_func();

  my_dylib_func();
}

mylib.c

#include <stdio.h>

void my_dylib_func() {
  printf("Now in dylib\n");
  third_party_func();
  printf("Now exiting dylib\n");
}

thirdparty_v1.c

#include <stdio.h>

void third_party_func() {
  printf("Third party func, v1\n");
}

thirdparty_v2.c

#include <stdio.h>

void third_party_func() {
  printf("Third party func, v2\n");
}

Now, let's first compile the files:

$ clang -c *.c
$ ls *.o
myapp.o         mylib.o         thirdparty_v1.o thirdparty_v2.o

Next, generate the static and dynamic libraries

$ ar -rcs libmystatic.a mylib.o thirdparty_v1.o
$ ld -dylib mylib.o thirdparty_v1.o -lc -o libmydynamic.dylib
$ ls libmy* | xargs file
libmydynamic.dylib: Mach-O 64-bit dynamically linked shared library x86_64
libmystatic.a:      current ar archive random library

Now, if we compile statically using the (implicitly) provided implementation:

clang -omyapp myapp.o -L. -lmystatic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib

Now, this was quite surprising to me, as I was expecting a "duplicate symbol" error. It turns out, ld on OSX silently replaces the symbols, causing the user's app to replace the symbols in my library. The reason is documented in the ld manpage:

ld will only pull .o files out of a static library if needed to resolve some symbol reference

This would correspond to point 1. above. (On a side note, running the above example on Linux sure gives a "duplicate symbol" error).

Now, let's link dynamically as in your example:

clang -omyapp myapp.o -L. -lmydynamic thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v1
Now exiting dylib

As you can see, your dynamic library now refer to its version (v1) of the static library, while the app itself will use the other (v2) version. This is probably what you want, and this is the default. The reason is of course that there are now two binaries, each with its own set of symbols. If we inspect the .dylib, we can see that it still exports the third party library:

$ nm libmydynamic.dylib 
0000000000000ec0 T _my_dylib_func
                 U _printf
0000000000000f00 T _third_party_func
                 U dyld_stub_binder

And sure, if we don't link to the static library in our app, the linker will find the symbol in the .dylib:

$ clang -omyapp myapp.o -L. -lmydynamic  && ./myapp
Hello from app
Third party func, v1
Now in dylib
Third party func, v1
Now exiting dylib

Now, if we don't want to expose to the app that we use some static library, we could hide it by not exporting its symbols (hide it as in not letting the app accidentally reference it, not hiding it truly):

$ ld -dylib mylib.o -unexported_symbol '_third_party_*' thirdparty_v1.o -lc -o libmydynamic_no3p.dylib
$ nm -A libmydyn*
...
libmydynamic.dylib: 0000000000000f00 T _third_party_func
libmydynamic_no3p.dylib: 0000000000000f00 t _third_party_func 

(A capital T means the symbol is public, a lowercase t means the symbol is private).

Let's try the last example again:

$ clang -omyapp myapp.o -L. -lmydynamic_no3p  && ./myapp
Undefined symbols for architecture x86_64:
  "_third_party_func", referenced from:
      _main in myapp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Now we have successfully hidden our third party static framework to client apps. Note that, again, normally you won't have to care.

What about if you really want 1. in dynamic frameworks

For example, your lib might need the exact version of the third party library that your client provides.

There is a linker flag for that too, of course: -undefined dynamic_lookup.

$ ld -dylib mylib.o -undefined dynamic -lc -o libmydynamic_undef.dylib
$ clang -omyapp myapp.o -L. -lmydynamic_undef thirdparty_v2.o && ./myapp
Hello from app
Third party func, v2
Now in dylib
Third party func, v2
Now exiting dylib

The downside is of course that it would fail at run-time if your client fails to include the static library.

Community
  • 1
  • 1
Krumelur
  • 31,081
  • 7
  • 77
  • 119
  • Thanks, Regarding the third bulletin in the "1" solution - how can i build my framework without the library linked in? if i do that my framework wont build (I am building dynamic framework) it will give "No such module GoogleMobileAds.framework". i have no problem telling the consumer to add the dependency but how do i build my framework? About the second solution my framework products are "MyFramework.framework" folder which dont include a *.a files so how can i change the internal classes but dylib and swiftmodule classes – Michael A Aug 16 '15 at 15:09
  • If you are building a dynamic framework you should be going the "2." route. Just don't export any symbols other than the ones you want to export. See here for how to hide symbols: https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/CppRuntimeEnv/Articles/SymbolVisibility.html – Krumelur Aug 16 '15 at 15:26
  • I program using Swift and from what i see it dont have the "Export" or the #define option. i tried setting the "symbols hidden by default" to true in xcode but it seem to have no effect. Further more the symbols which i want to hide are the third party library which i import in to the framework (not any of my written code) is that apply here to? how to properlly do it in Swift? I am struggling with this for few weeks any help will be highly appreciated – Michael A Aug 17 '15 at 07:26
  • 1
    I assumed your lib was written in ObjC. How did you get Swift classes into your static lib? I thought that was not supported by Xcode. – Krumelur Aug 17 '15 at 18:59
  • See the updated answer, I added an example that will help explaining it. – Krumelur Aug 17 '15 at 20:30
  • Really appreciate your help! Notice that i get an error for duplicate classes "Class GADGestureUtil is implemented in both..." not a method. You gave lots of info, regarding your last suggestion regarding "-undefined dynamic_lookup" can i tell xcode not to look for this speciffic framework classes? The third party framework in question is the GoogleMobileAds which contain lots of classes, Also about the "unexported_symbol" how is it done with xcode? cant find much about it online. – Michael A Aug 18 '15 at 07:56
  • Add it to"linker flags" on the build settings tab of your framework – Krumelur Aug 18 '15 at 08:00
  • I tried both -unexported_symbol 'GoogleMobileAds.framework' and -undefined dynamic_lookup 'GoogleMobileAds.framework' in the "other linker flags" in both cases there is a "mach-o linker" error saying "can't map file, errno=22 file 'GoogleMobileAds.framework' for architecture arm64, armv7" – Michael A Aug 18 '15 at 08:37
  • Either specify `-undefined dynamic_lookup` (without framework name) or `-unexported_symbol '_GAD*'`(i.e. the symbol name as a shell glob expression). – Krumelur Aug 18 '15 at 18:58
2

You can always use classes from framework like this:

import Alamofire

let request = Alamofire.Request(...)

And if you have Request class in your own framework, you can use it in the same way:

import YourFramework

let request = YourFramework.Request(...)

There there would not be any conflicts.

Deny
  • 1,441
  • 1
  • 13
  • 26
  • Tried it - dont work. in my case its the same framework "GoogleMobileAds" with same classes in both my framework and consumer project – Michael A Aug 16 '15 at 05:50
1

I had a similar issue before, but I was using Objective-C third party framework. The problem is solved by using the bridging header to specifically import the framework drag and drop to the consumer project, and leave your framework sort of capsulated. It's just my experience so it might not apply to your case but might as well share it here just in case it helps.

JDG
  • 540
  • 1
  • 5
  • 16
  • 1
    How do i build my framework without linking the third party framework in my code (it wont build unless i am linking its binary) – Michael A Aug 16 '15 at 12:37