Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to avoid duplicate symbols in project that will use my iOS framework and one of the dependencies?

Here is a quotation from the other post:

I'm working in a iOS project that includes a static library created by another company. The library include an old version of AFNeworking and I don't have any source files.

Now i need to use a more recent (and less bugged) version of afneworking, but i cannot include the same class twice in the project (of course) because all the "duplicate symbols"

My problem is that I'm preparing an iOS framework and I want to avoid this kind of situation in the future. I'm not talking about AFNetworking, but other quite popular iOS framework. In addition I applied some custom changes in the original framework code.

The one way to avoid "duplicate symbols" and "Class X is implemented in both Y and Z. One of the two will be used" that comes to my mind is to add some prefix to the original framework classes, but is this the right solution?

UPDATE 1:

I tried to apply John's solution but no joy. I have created a simplified project (here is the link to the repo) with two classes FrameworkClass which is present in framework target only, and SharedClass which is present in both framework and application targets, so maybe you can see if I'm doing something wrong. After application did launch I'm still getting: objc[96426]: Class SharedClass is implemented in both .../TestFramework.framework/TestFramework and .../SymbolsVisibilityTest.app/SymbolsVisibilityTest. One of the two will be used. Which one is undefined

UPDATE 2:

Here is my output from nm based on the provided sample project's framework-output:

0000000000007e14 t -[FrameworkClass doFramework]
0000000000007e68 t -[SharedClass doShared]
                 U _NSLog
                 U _NSStringFromSelector
00000000000081f0 s _OBJC_CLASS_$_FrameworkClass
                 U _OBJC_CLASS_$_NSObject
0000000000008240 s _OBJC_CLASS_$_SharedClass
00000000000081c8 s _OBJC_METACLASS_$_FrameworkClass
                 U _OBJC_METACLASS_$_NSObject
0000000000008218 s _OBJC_METACLASS_$_SharedClass
0000000000007fb0 s _TestFrameworkVersionNumber
0000000000007f70 s _TestFrameworkVersionString
                 U ___CFConstantStringClassReference
                 U __objc_empty_cache
                 U _objc_release
                 U _objc_retainAutoreleasedReturnValue
                 U dyld_stub_binder`

UPDATE 3:

I did manage to "hide" SharedClass symbols by applying the solution by @bleater and my output from nm is now:

         U _NSLog
         U _NSStringFromSelector
00001114 S _OBJC_CLASS_$_FrameworkClass
         U _OBJC_CLASS_$_NSObject
00001100 S _OBJC_METACLASS_$_FrameworkClass
         U _OBJC_METACLASS_$_NSObject
         U ___CFConstantStringClassReference
         U __objc_empty_cache
         U _objc_release
         U _objc_retainAutoreleasedReturnValue
         U dyld_stub_binder`

But I'm still getting double implementation warning in Xcode.

like image 597
Maciek Czarnik Avatar asked Dec 16 '14 16:12

Maciek Czarnik


1 Answers

You should limit the visibility of symbols in any framework or library you are developing. Set the default visibility to hidden, and then explicitly mark all the functions in the public interface as visible.

This avoids all the problems you have described. You can then include any version of any public library (AFNetworking, SQLite, etc.), without fear of future conflict because anything linking to your framework or library won't be able to "see" those symbols.

To set the default visibility to hidden you can go into the project settings and set "Symbols Hidden by Default" to YES. It is set to NO unless you change it.

enter image description here

There are at least a couple of ways to mark the symbols from your public interface as "Visible". One is by using an exports file, another is to go through and explicitly mark certain functions as visible:

#define EXPORT __attribute__((visibility("default")))
EXPORT int MyFunction1();

The define is obviously just for convenience. You define EXPORT once and then just add EXPORT to all of your public symbols.

You can find official apple documentation on this here:

Runtime Environment Programming Guide

Update:

I took a look at your sample project, and it looks like I pointed you in the wrong direction. It appears that you can only truly hide C and C++ symbols. So if your were having this problem with a C lib (like sqlite), setting the default visibility to hidden would work. It looks like the nature of the Objective C runtime prevents you from truly making the symbols invisible. You CAN mark the visibility on these symbols, but with Objective-C it appears that is just a way to have the linker enforce what you should or shouldn't be able to use from the library (while still leaving them visible).

So if you redefine a Objective-C symbol in different compilation unit with the same name (by perhaps compiling in a new version of a popular open source library), then you will still have a conflict.

I think your only solution at this point is to do what you first suggested and prefix the symbols you are including into your framework with a unique identifier. It isn't a very elegant solution, but with the limits of the objective C runtime I believe it is probably the best solution available.

like image 116
John Bowers Avatar answered Sep 28 '22 08:09

John Bowers