Expose header used in static framework - ios

I have a static framework B which I use inside a framework A. B has a class C I want to also expose in A, i.e. in the illustration below I want the App to be able to use the class.
App > framework A > static framework B > class C
If I try to include the header for C in the public headers of A, I get 'duplicate interface definition'. But as the code is compiled into A from B, I just need to expose the class interface – presumably that will allow the App to link correctly...?

I achieved this through a bit of a workaround.
When a static library/framework is compiled into another framework/static library, its symbols are still exposed (verify through nm <binary> | grep <symbol>). This means you just need your App code to know about those symbols – i.e. including the header for class C in framework A.
In your headers in framework A, you need to include the class C header. When compiling the framework you need to use "ClassC.h", but when the framework is being used you need <FrameworkName/ClassC.h>, as that is its location in the framework included by the App. To do this you use a macro which is removed in a run script phase.
So, in your framework A target build phases, make sure your header for Class C is in the 'public' section of your copy headers phase, then add a 'Run Script' phase and paste the following:
TARGET_MACRO="TARGET_FRAMEWORK"
cd -P "$BUILT_PRODUCTS_DIR/$WRAPPER_NAME/Headers/"
perl -0pi -e "s/\#if ${TARGET_MACRO}.*?\#else\r?\n?(.*?)\r?\n?\#endif/\1/sg" *.h
Change `TARGET_FRAMEWORK" to be whatever, or leave it.
In Framework A build settings, under preprocessor macros, add TARGET_FRAMEWORK=1 for all build configurations.
Then, in your Framework A headers that include Class C, use:
#if TARGET_FRAMEWORK
#import "ClassC.h"
#else
#import <FrameworkA/ClassC.h>
#endif

Related

Conditional import in Objective-C

My app is in swift and has some files from other modules in objective-c. App has 2 targets, lets say Target-1 and Target-2. When I build the app, a file called as "Target(number)-Swift.h" is generated in build folder.
I need to import this file into the objective-c class. Since there are 2 targets and the file name depends on target name, I need to do conditional import based on which target I am running. I want something like:
if running Target1
#import "Target1-Swift.h"
endIf
if running Target2
#import "Target2-Swift.h"
endIf
If I directly add #import "Target1-Swift.h" it works fine when I build Target1, but fails when I build Target2. I tried the following:
Code I tried:
#ifdef TARGET1_SWIFT_H
#import "Target1-Swift.h"
#endif
With this I don't see errors on imports, but the I see errors like "Use of undeclared identifier 'class_name'" for the classes declared in "-Swift.h", so looks like "-Swift.h" file it not being imported.
Any idea how can I fix this.
You can use Preprocessor macros for this.
Assign a flag for only one target. And use that flag for a condition to determine which target you're running on.
You can use it in the swift file as follow.

How to make custom C code into a SwiftPM package?

I am trying to package C code into a Swift module, let's call it CModule. Once I have put it into the base folder of a project (SwiftModule) and configured the Search Paths, I could get auto-complete to work in the Swift files, as well as detect errors/warnings.
The problem is, it won't recognize the module when it's imported, and Jump to Definition results in the following error:
Couldn't Generate Swift Representation
Error (from SourceKit):
"Could not load module: CModule"
CModule contains the following files:
src folder, containing the implementation and an umbrella header exposing all required functions (by importing other sub-headers) such
that main_header.h imports componentA.h, componentB.h etc
module.modulemap, containing only:
module CModule [system] {
header "src/main_header.h"
export *
}
The CModule folder is then put at the root of the SwiftModule project.
The framework and import search paths are defined as follows: $(PROJECT_DIR)/CModule (recursive)
Considering auto-complete and others work, I am confident the import search path is set correctly. No idea about the framework search path.
Additional details:
There are no spaces anywhere in my paths.
SwiftModule is a "framework" that will eventually be put on CocoaPods.
There are no public headers but the base SwiftModule header. CModule headers are all defined as Project headers.

Wrapping an existing C library as a Framework

I am trying to find a way of wrapping an existing C library with an Apple .framework structure. The key sticking point is avoiding the need to specify the Search Headers field in Settings.
Typically in a framework you specify something like:
#import <Foundation/Foundation.h>
where Foundation is the framework name and the .h file is an umbrella header.
When testing with existing code, for the sake of argument OpenSSL, the project is using #include <openssl/file.h> internally to refer to its files. Once you want to place this inside a framework for convenience every include naturally needs to be changed to <NameOfFramwork/openssl/file.h> or you must add the $(SRCROOT)/Path/To/Frameworks/NameOfFramework.framework/Headers to the search path. This is terribly inconvenient and kills a lot of the value of the framework format. It only becomes worse when you want to wrap multiple SDK versions of the library in an XCFramework.
I'm wondering specifically if the ModuleMap can help avoid the need to change the #includes? I've added a modulemap I'm creating as a test.
Experimental module.modulemap
framework module LibreSSL [extern_c] {
umbrella header "LibreSSL.h"
export *
module * { export * }
explicit module LibreSSL_Private [extern_c] {
umbrella "Headers/include"
link "LibreSSL"
export *
}
}
One unsatisfying solution is to switch to an XCFramework comprised of Libraries instead of Frameworks.
xcodebuild \
-create-xcframework \
-library <path/to/library> \
-header <path/to/headers> \
-output MyCool.xcframework
Then header search paths work correctly, but you lose the .modulemap and the nice framework structure that keeps everything together neatly per SDK build.

Duplicate symbols in workspace using same static library

A is main project. There are Four three subproject- B, C D added in main project A. All of three sub project generate a static library file ie .a
Subproject B require C in order to compile so C is added as a static library inside B subproject.D also require C to compile and generate lib file so C is also added inside D subproject as a static library.
Now main project A require B and D to compile and generate .app file. So both B and D added as a library files inside A project.
Now issue is while compiling project I am getting duplicate symbols linking error because C is added two times in main project via B and D.
duplicate symbol _OBJC_IVAR_$_Operation._requestKey in:
/Users/sandeep/Library/Developer/Xcode/DerivedData/square-fananfxhlvhxxgfqnkzssqckfmmj/Build/Products/Debug-iphonesimulator/libTeamCBService.a(Operation.o)
/Users/sandeep/Library/Developer/Xcode/DerivedData/square-fananfxhlvhxxgfqnkzssqckfmmj/Build/Products/Debug-iphonesimulator/libCBService.a(Operation.o)
duplicate symbol _OBJC_IVAR_$_Operation._sequenceNumber in:
/Users/sandeep/Library/Developer/Xcode/DerivedData/square-fananfxhlvhxxgfqnkzssqckfmmj/Build/Products/Debug-iphonesimulator/libTeamCBService.a(Operation.o)
Please let me know how can I resolved this problem so that there will be no kinking error. Is there a way to add C inside multi subproject to avoid duplication issue.

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.
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.
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.
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:
Categories on classes that has been renamed by NamespacedDependencies.h
Categories on classes not renamed by NamespacedDependencies.h
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_"

Resources