10

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.

Community
  • 1
  • 1
Maciek Czarnik
  • 5,950
  • 2
  • 37
  • 50
  • 1
    I'm not quite sure if this will help in your specific case, but guys at Sigmapoint solved it with prefixed symbols: http://blog.sigmapoint.pl/avoiding-dependency-collisions-in-ios-static-library-managed-by-cocoapods/ – Michał Ciuba Dec 18 '14 at 12:13
  • Couldn't you just open up the static library (using `ar`), remove the offending `.o` files, and then just link in your new ones? – Richard J. Ross III Jul 28 '15 at 21:42
  • I'm not sure what do you mean @RichardJ.RossIII, can you explain it a bit more? – Maciek Czarnik Jul 28 '15 at 21:44
  • @MaciekCzarnik a static library is just a collection of `.o` files, each of which is created by compiling each source code file of the library individually. If you were to extract the object files from the static library, and delete the ones that have the duplicate symbols (e.g. every `.o` file that has the prefix `AF*`, then you could simply link in your newer AFNetworking version, and everything should *just work*. EDIT: Misread the OP a bit. My solution works for the other end of this problem, not the producing end. – Richard J. Ross III Jul 28 '15 at 21:48
  • Got it, thanks for the clarification @RichardJ.RossIII! Although I cannot assume/require anything from a client's project, problems like my framework requires SomeFramework v1.* and client uses SomeFramework v2.* will still persist – Maciek Czarnik Jul 28 '15 at 21:54

2 Answers2

8

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.

John Bowers
  • 1,695
  • 1
  • 16
  • 26
  • Sounds reasonable, I will try it :)! – Maciek Czarnik Dec 17 '14 at 08:21
  • I tried it John, doesnt seem to work for me :/ I have prepared a simplified project I will appreciate if you can take a look and tell me what am I doing wrong (see update to my original question) :) – Maciek Czarnik Dec 17 '14 at 15:14
  • I am going to try and update this answer so it flows better from top to bottom. Setting default visibility to hidden on a library is still good practice, but it isn't quite the hard boundary with ObjC as it is in C/C++ – John Bowers Dec 17 '14 at 20:39
  • Thanks John, you have my upvote for the given effort :)! But I will hold myself on marking it as a right answer for the while. Thanks! – Maciek Czarnik Dec 18 '14 at 11:29
  • Ah... And I added Update 3, I think I'm getting closer ;) – Maciek Czarnik Dec 18 '14 at 11:38
1

So the blog post by Kamil Burczyk was a good starting point, thanks for the hint Michał Ciuba! It has covered most of the symbols, but it didn't cope with categories and class clusters. You can see what category methods are still exposed without any change by invoking nm with parameter list sth like:

nm MyLibrary | grep \( | grep -v "\[LIBRARYPREFIX" | grep -v \(MyLibrary | grep -v ") prefix_"

When it comes to categories we have 3 groups of categories, and they all require a specific, different approach:

  1. Categories on classes that has been renamed by NamespacedDependencies.h
  2. Categories on classes not renamed by NamespacedDependencies.h
  3. Categories on class clusters like NSString, NSArray...

Ad 1.

Everything is ok - class name will be prefixed so category will exist on prefixed sumbol in object file

Ad 2.

This problem occours whenever inside of the dependency we have category on a class like NSObject. It would be exposed without any change in object file, thus would cause a conflict. My approach was to internally rename NSObject to PREFIX_NSObject, this ofcourse requires me also to create and add the PREFIX_NSObject class implementation to the project (empty implementation, just a subclass of original NSObject)

#import "PREFIX_NSObject.h"
#ifndef NSValueTransformer
#define NSValueTransformer __NS_SYMBOL(NSObject)
#endif

Ad 3.

We cannot apply Ad 2. approach here. Actual objects created by let's say PREFIX_NSArray class methods are still of type that wont derive from my presumable PREFIX_NSArray class, so this doesn't make sense as category methods defined on PREFIX_NSArray won't be visible on NSArray derived objects. I ended up by manually prefixing methods of those categories in source code.

It's kind of crazy workflow, but at least gives warranty that everything will be 'invisible' and won't cause a conflict.

It's always good idea to run nm to check if all category symbols are hidden:

nm MyLibrary | grep \( | grep -v "\[LIBRARYPREFIX" | grep -v \(MyLibrary | grep -v ") prefix_"

Maciek Czarnik
  • 5,950
  • 2
  • 37
  • 50