Check for framework's existence at compile time? - ios

I'm working on an open-source project that can optionally use a closed-source framework. If the closed-source framework is included in the project, there will be additional functionality. But if the framework isn't included in the project, the project should still compile properly.
How do I check at compile-time if the framework is included in the project?
Basically, I want to do something like this:
#ifdef _MY_FRAMEWORK_EXISTS
#import <MyFramework/MyFramework.h>
#endif
I've seen older questions from 2 years ago like this one, but no answer has surfaced so I might be missing something new now.
I DON'T want to check at run-time via NSClassFromString(), because this will fail at compile time when I try to import MyFramework and it doesn't exist.

You can check for the presence of a header file using the __has_include language extension.
http://clang.llvm.org/docs/LanguageExtensions.html#include-file-checking-macros
However, that only tells you if the header file is installed. It can't tell you if "Link Binary With Libraries" has linked to its framework.

I recommend reading the Mac Developer Library : Framework Programming Guide (which includes a section on Weak Linking).
What do you mean by "exists" or "included in the project"? Do you mean added to the "Link Binary With Libraries" build phase (as described by Including Frameworks)? All that does is affect the linking, not the compilation, build phase. To see this, build. Then, search for -framework in the build log of Xcode's Log Navigator.
So, yes, if you want to affect the compilation of the code you provided, you could manually define the macro _MY_FRAMEWORK_EXISTS.
I don't really understand what you are trying to do. Can you explain what you want at a higher level? Perhaps, there's a better way to go about it.
"Minimal overhead" is nice, but too much magic might be confusing. For example, Xcode's magic hides what really happens when including a framework.
I also recommend checking out how the Facebook SDK for iOS works for high & low-level ideas. It might do the kinds of things you want to do.

Related

Make framework compatible for different Xcode versions

I have a specific scenario I am trying to overcome and have yet to find a solution (if one exists) to my problem.
I am building a framework that can be used inside of other applications. This framework contains static libraries.
Now, inside my framework, if I want to add classes that use APIs from Xcode12, I would also want my framework to be able to compile in Xcode11.
This could obviously lead to compilation issues as I have no control of my framework inside other applications and as my framework will already be turned into a binary file (.a). Adding any build script to the frameworks' build phases won't be relevant.
I have been trying to look for similar scenarios or solutions for this problem, but have yet to find anything substantial.
Is there any way to overcome this or am I trying to do something that is impossible?

The bundle at … contains disallowed file 'Frameworks'

I'm trying to deploy a .dylib library wrapped in a framework, and I'm experiencing the following issue: 'ERROR ITMS-90206: "Invalid Bundle. The bundle at X.app/Frameworks/PoemsRecommender.framework' contains disallowed file 'Frameworks'."
It looks like there's a framework nested in a framework:
Things I tried:
Remove "Frameworks" folder in the "Run Script" phase (it actually helps with submission but the app crashes because it can't find the dylib which is in the Frameworks folder)
Change "Frameworks" to "Shared Frameworks" in "Embed Frameworks"
Disable "Always Embed Swift.."
Reason
The error message indicates that you are trying to package a framework that has its own frameworks embedded inside.
This is not currently supported through traditional framework embedding
The fix is, unfortunately, to link all frameworks separately in your main target.
How do I know?
When you see this error X.app/Frameworks/PoemsRecommender.framework contains disallowed file 'Frameworks'."
Apple is explicitly saying that a framework that embeds a "Frameworks" folder is disallowed. There is a great discussion here that goes into a lot more detail.
Recommended Solution
Package your frameworks separately and link them all manually. Apple has good guidelines here
Not Recommended but will achieve your goal
If you must use a single framework, there is quite a bit of work involved. Effectively you want to create an Umbrella framework that will mask all underlying frameworks. This can get messy if you ever decide to link those sub-frameworks in other parts of your application. Since your question is vague on the details I cannot ascertain if this will affect you.
There are a number of resources that can guide you through the laborious process.
Effectively the steps involve creating an aggregate target that can use a build script to bind all your frameworks at runtime.
I have done this in the past and we ran into lots of issues on the way and I would not recommend this approach. In the past we had a white-labelled SDK that consisted of a number of internal frameworks that were binded by an aggregate target. More often than not it was difficult to maintain and difficult to understand by new team members. Eventually we just migrated to multiple frameworks.
I think Apple has their own tooling to support how they do it but, sadly, it is not available to us peasants.

Don't we need to link framework to XCode project anymore?

Base on this question
Why don't iOS framework dependencies need to be explicitly linked to a static library
I read the selected answer and still don't understand so I made an example project
Test Project on Github
In the test project, I remove all framework from Link Binary With Libraries and File navigation for both main project and the static library (including Foundation.framework and UIKit.framework too), basically, both project link to 0 frameworks.
Questions are
In static library, it's including MapKit/MapKit.h without referencing the Mapkit.framework to the project, why is its still working?
In main project, I remove UIKit.framework and Foundation.framework from the project, why is it still working?
Since it's working for now, will there be any issue later?
Thank you for your comment.
P.S. By working, I mean I can run on the simulator and I can archive the main project without any error.
Edit 25/07/2014
I tried with the real app that I'm working on, it's the same.
I highlight Foundation, UIKit, CoreData and 10 another frameworks in File Navigation, well, all of them.
Uncheck the target in Utilities Panel --> Target Membership
Build : Pass, Run : Pass
Every functionality of my app is still working as expected. I don't get this.
Check your project build settings. Underneath LLVM 5.1 — Language — Modules you should see the option 'Link Frameworks Automatically'. In your case it sounds like it's set to 'YES', the default.
In that case, instead of producing an error when you reference a class that the compiler doesn't know, it'll figure out which Framework contains that class and link it. In your code it'll be MKMapView or one of the other MapKit classes that triggers the linkage.
EDIT: from the relevant 'What's New?' document:
Auto Linking is enabled for frameworks imported by code modules. When
a source file includes a header from a framework that supports
modules, the compiler generates extra information in the object file
to automatically link in that framework. The result is that, in most
cases, you will not need to specify a separate list of the frameworks
to link with your target when you use a framework API that supports
modules.
Another way of looking at it is that the compiler is smart enough to mutate #import to #import when the framework has been built appropriately. All system frameworks have been.
To elaborate #Tommy's answer, a framework that supports modules satisfies the following 2 conditions:
Under Build Settings > Packaging
Define Modules is set to YES
Module Map File exists.
So, if you're certain that the framework you're using in your code modularizes like that, you can choose to not explicitly add it in the link phase as it will be automatically added as long as in the project file, under Apple Clang - Language - Modules, The option Link Frameworks Automatically is set to YES.

tgmath.h doesn't work if modules are enabled

I looked into using tgmath.h to deal with the CGFloat typedef float/double mess when dealing with arm64.
This answer has a pretty good description of how to use it, except that it didn't work at all for me. No matter what, my code was still calling the math.h functions.
After spending some time looking at all of the project compiler settings, I found that disabling the "Modules" feature (#import vs #import - iOS 7) makes it all work. More specifically, the option in the project settings is called Enable Modules(C and Objective-C) in the Apple LLVM 5.1 - Language - Modules dropdown.
To see a quick example of this issue, download a project that uses tgmath, such as MBProgressHUD, and see what happens when you enable the modules project setting. The tgmath.h calls get replaced with regular math.h calls.
My question is:
Why do modules prevent tgmath from being imported properly?
Is there a way to get around it and use both tgmath and modules? I would like to still be able to use them.
I'm not sure what's causing the issue, but as a workaround you could at least disable modules for only the file(s) where you're using tgmath.h:
Navigate to the target's Build Phases tab in Xcode.
Under the Compile Sources phase, locate the source files.
Double-click the source file and type -fno-modules in the Compiler Flags popover to disable Clang modules for that file.
At least this way you would still get the benefits of modules in most of your project. (This is assuming, of course, you don't need tgmath.h in the majority of your source files.)
It might already be in your math library under the name ctgmath: Link

Includes fail to resolve correctly in Xcode workspace

I'm having trouble linking to a static library in Xcode 5. I did read the chapter on building and using a static library in the Pro iOS 5 Tools. What it told me to do after creating your static library, was to link against the framework in the Build Phases tab. That part is pretty straight forward. Then it says in the Build Settings, under "Header Search Paths", to add:
$(BUILT_PRODUCTS_DIR)
Then in order to use my static library, I just import it like so:
#import "ConversionCalculator/ConversionCalculator.h"
So this used to work up until yesterday. I tried adding a new static library to my project which didn't seem to work. So I cleaned my project, and tried rebuilding again, but now Xcode complains about #import "ConversionCalculator/ConversionCalculator.h". It says
Lexical or Preprocessor issue. 'ConversionCalculator/ConversionCalculator.h' file could not be found.
I was wondering if anyone has any tips on debugging this. I've looked through different tutorials like this:
http://blog.stevex.net/2012/04/static-libraries-in-xcode/
http://cocoamanifest.net/articles/2011/06/library-management-with-xcode-workspaces.html
http://www.raywenderlich.com/41377/creating-a-status-library-in-ios-tutorial
But I can't seem to find the "golden way" to link to a static lib, or how to troubleshoot why Xcode cannot find my file. My file structure is setup on my machine like so:
Projects\
DistanceCalculator\DistanceCalculator.xcodeproj
ConversionCalculator\ConversionCalculator.xcodeproj
DistanceCalculator.workspace
Like I said, this all used to work too when I followed the tutorial in Pro iOS 5 Tools book. Now I don't know why my workspace cannot find ConversionCalculator when it has used it before. The part I find hard is different articles say different things about the header search path, and I'm not sure what the best way to populate that field is. Any thoughts? Thanks in advance!
Edit:
I'll add that I can build for the device without errors. But when I switch to the iPhone simulator, it gives me that error about not being able to find the file. I also see that I get this warning:
ConversionCalculator was rejected as an implicit dependency for 'libConversionCalculator.a' because its architectures 'i386' didn't contain all required architectures 'i386 x86_64'
Looking at that, I'm not sure what that means. If it means that my library is not being built for all architectures, I just tried creating a Target that builds for all architectures according to the wenderlich article in the above link. That seems to work as when I go to the dervieddata folder, I see for debug, release, and universal, I see the libConversionCalculator.a file. But then when I go back into the workspace and try to rebuild the project for the simulator, I get that could not find file error and the implicit warning.
Edit #2:
I just saw a warning flag on Xcode that says upgrade to recommended Build Settings. Now I get no errors. Not sure what happened... but I guess no errors is good.
I would follow the description available in Xcode's Help topic "Linking Against Your Library" in chapter Configuration Your Application in Introduction to Using Static Libraries in iOS
(you may search within Xcode Help, too).
Except that I would recommend to include your headers from libraries always using angle brackets:
#import <ConversionCalculator/ConversionCalculator.h>
Using double quotes may inadvertently search and find files with the same name in some sub-folder relative to the file where this import directive is written. Only after there was no file in any sub-folder the preprocessor starts searching with the specified header search paths.
Using angle brackets, the preprocessor immediately searches only at the specified header search paths.
So, since you actually want to find headers for the corresponding library, always use angle brackets.
Note: If you follow the recommendation to create a static library project, you don't need to explicitly set a header search path in the target that links agains the library: Xcode will already add a search path:
$(BUILT_PRODUCTS_DIR)/includes
Your library headers are located in
$(BUILT_PRODUCTS_DIR)/includes/<product-name>
which are placed there through the "Copy-Files" phase of the iOS static library target, whose "Destination" is set to "Products Directory" and whose "Subpath" is set to "includes/${PRODUCT_NAME}" per default.
You may change these default settings to other reasonable and sensible values. (if you do, consider the the consequences for Xcode's default search paths!)

Resources