How does iOS and Xcode link custom frameworks - ios

I have a project with includes custom frameworks, but these custom frameworks also include custom frameworks.
Here's a sample structure, where the bullets are the included frameworks:
App
FrameworkA
FrameworkB
FrameworkC
FrameworkA
FrameworkC
FrameworkB
FrameworkC
Since FrameworkA and FrameworkB link FrameworkC, along with my App using Frameworks A, B, and C; are they all linked together or is the framework copied in multiple places?
Does this increase the size of the app?
In this example FrameworkC has assets, if it's copied multiple times wouldn't that unnecessarily duplicate data? Or this there a better way?

iOS frameworks are a collection of header files and a fat static archive library. iOS does not support frameworks containing shared libraries.
A fat static archive library is a collection of multi-architecture object files, the object files can be extracted as needed by the linker to create an executable artifact. iOS executables are self contained executables (except for system libraries which are shared objects).
Fat archives can be examined with lipo.
cd FrameworkA.framework
lipo -info FrameworkA
Architectures in the fat file: FrameworkA are: armv7 armv7s i386 arm64
When you create a Framework, the linker tool is not used because the frameworks (in your case FrameworkA and FrameworkB) are not executable artifacts. The dependent framework is included in the framework project because the compiler requires the header files to create the Framework A and B object files. Any references to symbols in frameworkC remain unresolved.
If you inspect the contents of FrameworkA or FrameworkB using the "lipo" and "ar" tools and extract an object file, then dump the object file symbols using "nm", you will notice any references to symbols in FrameworkC remain unresolved.
Note: You need to install the Xcode commandline tools to do this.
cd FrameworkA.framework
lipo FrameworkA -thin armv7 -output FrameworkA_armv7.a
ar -t FrameworkA_armv7.a
objectA1.o
objectA2.o
objectA3.o
ar -x FrameworkA_armv7.a objectA1.o
xcrun --sdk iphoneos nm -p objectA1.o
...
00000188 T FrameworkAFunction
U FrameworkCFunction
...
This is why when you create an App, you need all three Frameworks (A,B and C) included in the project. When the linker reads an unresolved symbol in one of the App object files it will search framework A and B. When the symbol is resolved, it reads in the whole object file from the archive that contains that symbol. The linker must then resolve any unresolved dependent symbols in that object. If one of those unresolved symbols is in FrameworkC, it will pull in the object containing the dependent symbol from FrameworkC
So to answer your questions:
Frameworks A,B and C are linked when the App is linked, only the objects required to resolve all symbols are copied from the Frameworks into the app executable.
Yes, copying in objects increases the size of executables, but there are no multiple copies of any of the objects.
The assets in FrameworkC are only copied once, when the final App bundle is assembled by xcode.

Related

'MyClass' is unavailable: cannot find Swift declaration for this class - Simulator

I have my framework and I am supporting different architectures. For supporting simulator I am using x86_64 in valid architectures.
But when I use my framework in the app, I get the error while running the app on simulator :
'MyClass(In framework)' is unavailable: cannot find Swift declaration
for this class
Note : It works fine on device.
If memory serves me right, since 6-th version Xcode doesn't support so-called "universal" frameworks (frameworks that contains architectures for arm-family and x86_64/i386). So now when you build a framework Xcode will make two separate bundles for iphoneos and iphonesimulator (you can find them under corresponding folders in your derived data folder). The architectures these frameworks will contain depends on ARCHS variable. By default it's set to $ARCHS_STANDARD, that varies between platforms. You can play around with this setting, mixing architectures you really need, but Xcode will fail at build time if these architectures are incompatible (presumably x86_64 and arm-family architectures are considered incompatible).
To get round this limitation you actually have to do a little bit of "hard-work" yourself and "merge" framework bundles for simulators and devices manually.
1. Build your framework for iOS device and simulator
Here is nothing fancy, just go to your target build settings and ensure that "Build Active Architecture Only" (ONLY_ACTIVE_ARCH) is set to NO and add all required architectures for "Valid Architectures"(VALID_ARCHS, you have already done that):
Now build your framework, find the bundle file under the Product group in Xcode, and open it in Finder:
You should find two folders, one for each set of architectures:
2. Merge two frameworks into one
Now go into Debug-iphoneos folder, copy framework from there and paste it somewhere else, for example in the parent folder:
It will contain our universal framework in a short while. In order to do that we need first create universal dynamic library with the lipo tool. Open terminal, navigate to the folder where you are now (with the copied framework file) and type as follow:
$ lipo -create -output 'MyFramework' 'Debug-iphoneos/MyFramework.framework/MyFramework' 'Debug-iphonesimulators/MyFramework.framework/MyFramework'
If you are not in the derived data folder your paths for the framework libraries will differ of course. This command will produce new universal library containing both sets of architectures. Now just drag and drop it into your framework bundle and replace the existing one.
It's likely that your framework has architecture slices under a folder like MyFramework.framework/Modules/MyFramework.swiftmodule. Our universal framework should have slices for all supported architectures. You should already have arm slices inside, since we copied our framework from the Debug-iphoneos folder, so just find the same folder for the Debug-iphonesimulator and copy files from the folder to the destination framework's folder. You now should have this folder look something like that:
That's it! Now you have a universal framework that should work properly on both an iOS simulator and an iOS device.
3. Slice your framework when submitting to the Appstore
If you try to submit an application alongside a universal framework, it will be automatically rejected. So before submitting you will need to extract only devices' architectures one by one and then merge them into one framework using lipo:
$lipo -extract 'arm' 'MyFramework.framework/MyFramework' -output 'MyFramework-arm'
$lipo -extract 'arm64' 'MyFramework.framework/MyFramework' -output 'MyFramework-arm64'
$lipo -create -output 'MyFramework' 'MyFramework-arm' 'MyFramework-arm64'
Then copy it into your framework and remove redundant slices.
P.S.
Hopefully it helps, however this is kind of well known issue and there is already a lot of solutions in the internet. I suggest you googling terms like 'Create fat/universal framework iOS', and I believe you will find a lot of working scripts that will do all this stuff for you. Here is one I find the most helpful.
I see two possible options:
Your device and your simulator use different iOS versions, and your framework is using something from the latest iOS versions (also, I see that you have the option "Build Active Architecture only" switched to 'No'. Try to switch it back to 'Yes' and make a clean build. Probably, the compiler will find an issue, but it depends on how you link your framework to the app).;
Your framework built for a specific device. Try to switch to the 'framework' target, switch destination for build to 'Generic iOS device' and make clean builds for the 'framework' and the app.
Also, it will help if you update the question with the brief description of how you link your framework (via Workspace / via binary / via CocoaPods / etc.).
Usually, such issues caused by some stupid things, and you should check all theories, even dump or misleading at first sight :)

Wrapping static library in Cocoa Touch Framework

I have a fat static library with 2 architecture slices (armv7, arm64).
I'm trying to make it work with swift and wrap it into Cocoa Touch Framework.
What I do:
Create Cocoa Touch Framework project
Drag .a static library with headers
Set OTHER_LDFLAGS to -all_load
Set ONLY_ACTIVE_ARCH to NO
Set VALID_ARCHS and ARCHS to armv7 and arm64
Build with Release build configuration
Grab .framework file from DerivedData/../../Products/
When I put this .framework into my swift project, add to Embedded Binaries section in my targets general settings, import framework and use one of its classes, I'm getting undefined symbols for architecture arm64 or undefined symbols for architecture armv7.
EDIT:
Not sure if it helps but I've noticed that size of static library is about 34MB but size of generated .framework is about 12MB.
EDIT 2:
I ran nm -arch arm64 -g myLibraryName on both static library and generated dynamic library. The dynamic library doesn't contain all symbols that static library has. Seems like XCode build process strips lots of them.
I think you have also forgotten to add :
Project->Target->Build Phases->Link Binary With Libraries:
add + the: libz.dylib or libz.tbd
(Since Xcode 7 the *.dylib files are now *.tbd files)
Be sure to also clean the folder: /User/yourname/Library/Developer/XCode/DerivedData
P.S: If you want you should also be able to add the libz.tbd from "Other Linker Flags" in the Build Settings by adding the argument -lz.

Objective-C / iOS - Resolve duplicated symbols inside a static library

My iOS project used a 3rd party (not open source) static library (i.e. libA.a), and this libA.a used CocoaLumberjack, it compiled CocoaLumberjack directly into itself, and the version of CocoaLumberjack is unclear.
Now I also want to use CocoaLumberjack to track logs in my program, and it will result duplicate symbol errors if I install CocoaLumberjack via CocoaPods.
Questions:
Is there a way to hide
the CocoaLumberjack symbols in libA.a so that Xcode won't report symbol errors?
Any other file logger libraries that can be recommended?
Now I am looking through symbols in libA.a, contrasting it with the source of CocoaLumberjack, and I am closed to find the version of CocoaLumberjack libA.a used, my next step should be only including header files of CocoaLumberjack in my project. It should work, but I don't like this way.
You can unpack the object files from the static library and repack it without the CocoaLumberpack object files.
Something like:
$ ar x libA.a
$ rm cococaLumberjackFile*.o # Whatever they are called
$ ar cr libA.a *.o
If the static library is fat (contains multiple CPU architectures) then this becomes much more difficult and involves lipo and much pain.
EDIT: Just go ahead and use CocoaLumberjack in your code and link with libA.a. It will provide the objects for both the 3rd-party library and CocoaLumberjack.

Symbols not found for architecture when building Cocoa Framework used in Static Library subproject

I built a pretty simple Cocoa Framework in Xcode for use on an iOS device (not simulator). I dropped this framework into another project that builds a static library. When I build this static library it succeeds without error. No problems yet.
NOW:
I have an actual iOS app project that includes the static library project as a subproject.
When I try building this app I get 'no symbols found for architecture' errors regarding the two classes I am trying to use that are defined in my cocoa framework:
I'm not only building the framework for the active architecture:
I've verified that it is indeed built for both armv7 and arm64 in Terminal:
Kevins-MBP-2:ASI.framework kevin$ lipo -info ASI
Architectures in the fat file: ASI are: armv7 arm64
The only way to get this building successfully is if I also drop my cocoa framework into the iOS app project, in addition to it already existing in my static library project. I feel like I shouldn't have to have the framework in both projects. Only the static library actually uses the framework. The iOS app project does not need to know about the framework.
Static libraries don't contain dependent libraries, so whenever you link against a static library you need to supply the dependent libraries they use to the linker.
Basically you do need to also link the executable against this Cocoa Framework as you have stated in the last sentence of your question.

Explaining Clang dynamic/static library linking process

I'm building my own library and I'm quite confused with how does the final executable linking process work.
I have a MyLibrary.framework which uses CoreLocation header.
When I set Mach-o type to static the framework builds without problem, even though I didn't link the CoreLocation.framework in "Linked Frameworks and Libraries".
When I link MyLibrary.framework to my iOS test project, I have to add CoreLocation.framework, because otherwise I get unreferenced symbols errors.
Question
Why doesn't building my static library need the reference the CoreLocation, and why does building my app require linking to CoreLocation ? What happens there ?
The answer from Mecki here:
Objective-C categories in static library
Explains the compiling/linking process very well. A .a file is an (.a)rchive of .o files.
each .o files contains not only the symbols it has, but also the symbols it needs.
Only during the executable linking process the linker resolves all the symbols in the executable file, which references a static library, which references the CoreLocation library.

Resources