Why are iOS frameworks I'm linking as Optional being treated as required when my framework is imported elsewhere? - option-type

I'm trying to add a feature to an existing iOS framework published by my company. The new feature requires that we make use of a number of other, third party-supplied frameworks. We want to ensure that our customers are not required to deploy those frameworks if they do not want to activate the new feature.
I've configured each of these frameworks as Optional when I reference them in the Target / General / Linked Frameworks and Libraries section of my own framework. I also mark them as Optional in the Target / Build Phases / Link Binary With Libraries section of my framework. I'm expecting that this will mean my framework can be imported in an xcodebuild when those frameworks are not present.
This works on the machine where I build my framework, but as soon as anyone else tries to import my new framework their Xcode objects to the import statement with a message "Missing required module 'x'" (where x is the top level third party framework that my framework imports).
I tried removing them and adding linker directives of the form "-weak_framework {name}" to the build, but I can see from the xcodebuild log that the Optional settings simply generate these anyway, and more conveniently.
My framework uses Swift 5, in case it's relevant.
What am I missing?
Many thanks for any clues.

If you create your own framework which uses third party frameworks and you want to make those third party frameworks optional (but at the same time required only for some features of your framework) then I think you need to do few things.
First make sure that those third party frameworks that should be optional use -weak_framework option for linking. It seems that you already did that. If you are adding those third party frameworks with cocoapods then you will probably need to add at the end of your Podfile script such as the one below because cocoapods tends to override those changes and reverts -weak_framework to -framework if you made that change manually.
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
xcconfig_path = config.base_configuration_reference.real_path
xcconfig = File.read(xcconfig_path)
xcconfig_mod = xcconfig.gsub(/-framework "ThirdPartyFrameworkName"/, "-weak_framework \"ThirdPartyFrameworkName\"")
File.open(xcconfig_path, "w") { |file| file << xcconfig_mod }
end
end
end
Second thing is that you will need to import this third party framework in your framework as implementation only framework and you can do this like below:
#_implementationOnly import ThirdPartyFrameworkName
Last thing is that in you code you will need to check if this third party framework is actually loaded so that your code will not crash in case when someone will not add this third party framework to his application. So the optional feature in your framework that uses third party framework should first check for example if given class of this external framework exists before using it like below:
if NSClassFromString("ClassNameFromThirdPartyFramework") == nil {
return // third party framework not available
}
// do something with ClassNameFromThirdPartyFramework here
If you perform all those steps it should work.

Related

How do I add a framework dependency to a test target?

I have created a framework that contains code useful for testing (a bunch of convenience initializers, for example). Now I would like to import that framework in a test target but there doesn't seem to be a way to do this in Xcode. Just importing it with import Dependency gives me the "Module not found" error when building.
The General tab of the test target does not have any settings for that (or any settings):
The Build Phase tab has a Dependencies section, but the framework I want to add does not show up in the list when I press the + button:
There's also a Link Binary with Libraries section, but when I try to select the framework there Xcode says that file is already being linked (where?):
I can import and use the framework if I add it in the Frameworks, Libraries and Embedded Content section of the main target, but I don't want to do this since the framework is meant to be used only in the test code. I don't see the reason to pollute the namespace with all the extra initializers. I could probably try to check if no XCTest classes are available at runtime and crash, but that seems like a workaround rather than a solution.
Any advice?
P.S. I'm using the latest version of Xcode (11.3.1).
If you have a source code you are able to use Explicit dependency[About] with cross-project reference
drag and drop dependency project inside a test target
Build Phases -> Dependencies -> Plus(If you didn't include dependency into test target on previous step)
Import
[Mixing Objective-C and Swift]
Xcode 12
Drag and drop the framework into folder where tests are located.
Go to YourAppTests target settings: Build phases -> Link Binary With Libraries – here your framework should be listed, if not tap on + button -> Add other (on the bottom) and choose your framework from project's test folder
Done!
PS: most likely works for Xcode 11 as well

How do I embed a 3rd party framework inside a framework target in Xcode

I have created a framework target (BourbonKit) inside of my iOS project. BourbonKit needs to include a 3rd party framework. I'm able to add the 3rd party framework to that target and setup the framework search path correctly, but when I try to import the 3rd party framework inside one of BourbonKit's classes I'll get a compile error stating that the 3rd party framework header can't be found.
I know Apple discourages creating umbrella frameworks, but I don't see an alternative in my case and I'm trying to stay away from using CocoaPods and Carthage.
I got this working by adding the 3rd party framework as a linked library to my framework AND then added it's headers to the "project scope" (see attached image)
I then set the framework search path to the directory where the framework is located. Xcode 6 would automatically set this for you, but in 7b3 you have to set it manually.
Now I'm able to import the 3rd party framework anywhere in my public/private classes that I want to.

Creating static framework for OS X

With the use of a mild hack, it is possible to make static frameworks for iOS. Static frameworks are quite convenient to use: they can simply be dropped into new projects without extra steps (like adding them to the build and adding header search paths).
I've recently started doing OS X programming, and the first thing I noticed was that static frameworks don't seem to be available. Dynamic frameworks are obviously available and recommended, but as I want to make a little private framework intended for application use (not installation in /Library/Frameworks), using a dynamic framework in new application projects still requires a bunch of extra steps.
In my ideal world, I'd create a static framework (a framework which contains header files and a compiled .a file), drag & drop the framework onto a new project, and start coding. Is there any way to make such a static framework on OS X?
P.S. I already tried setting the Mach-O output type to "static library" in a normal framework project, but I just get the error Framework target has invalid MACH_O_TYPE value of 'staticlib'..
You can create a dynamic framework on Mac OS X. In your dynamic framework you can set the LD_DYLIB_INSTALL_NAME as #rpath/Foo.framework/Versions/A/Foo
If you have an app that wants to link with this framework then you make sure you run the
install_name_tool -add_rpath <rpath> <full-path-to-app-binary>
So if I had Foo.app
install_name_tool -add_rpath Foo.app/Contents/Library Foo.app/Contents/MacOS/Foo
Now if you just copy your Foo.framework into Contents/Library it should get loaded and everything should work.
I hope this helps.
Probably simpler would be to use a static library with public headers. When you build the static lib you can have Xcode copy the headers for you automatically. And in your target you can add the folder to your search path.
If you use a static library Xcode will strip away some dead code that your app doesn't really need but is compiled into the static lib.
Static frameworks aren't really supported on OS X. They're fairly brittle anyway, and solve a specific problem that exists on iOS but not on OS X.
If you're looking to make it easy for developers to use a library you create, you have a couple options:
Use Cocoapods. They have a tutorial for publishing your library on CocoaPods. This is probably the easiest way to distribute a library on OS X.
Package your library as a framework. If you set the install name correctly (to #rpath/<library name>), the downstream developer merely needs to copy the framework into their Xcode project and set the runtime search path of their application to #executable_path/../Frameworks).

Omnigroup frameworks dependency

The OmniUnzip framework is linked to 2 other frameworks : OmniBase and OmniFoundation.
Because I just need the OmniUnzip framework in my app. Is it enough to just link it to my target and copy it into my app bundle ?
Or do I need to also copy the OmniBase and OmniFoundation frameworks in my app bundle ?
If it's dependent on the other frameworks, you need to include them too.
Easy enough to test - if you get linker errors without them, but don't with them, then you need them. :)
Just because you aren't calling methods in those other frameworks doesn't mean the OmniUnzip framework isn't.

Using frameworks in a command line tool

I've built a command-line utility (Foundation tool) in Xcode, using Cocoa. The tool makes use of a 3rd party framework.
Everything works OK in Xcode, but how do I deploy this program?
If I run the app from Finder, it can't find the library because it's looking in ../Frameworks/etc. Can I statically link in the 3rd party framework?
Unfortunately, there is no way to bundle a framework with a command-line Utility in OS X and I suspect that the framework you're linking to is expecting to be bundled in the app bundle's Frameworks/ directory. If you have access to the framework source code, you can compile a static library and statically link it to your application (or include the source in your application target directly). If you don't have the source code or you don't want to statically link the library for some reason, there are two remaining options:
If you have access to the system-wide /Library/Frameworks folder, you can install the 3rd party framework there. This require that the framework's Installation Path (the INSTALL_PATH build setting) be set to /Library/Frameworks at build time or that you use the install_name_tool to change the frameworks install path to /Library/Frameworks (if you don't have the framework's source code).
Build an application bundle (as if you were building a GUI app) with your command-line utility as the app bundle's executable (i.e. in AppBundle.app/Contents/MacOS/). You can then copy the 3rd party framework to the app bundle's frameworks directory. You can then put the app bundle anywhere you want and create a symbolic link to the command line utility.
Option 1 is definitely the more accepted approach, but I've used option 2 when there was a valid reason.
You can find more information on building, linking, and installing frameworks in Apple's Frameworks Programming Guide.
Another way, if you have the source code for the framework, is to add a static library target and build a static lib from it. Then you can statically link it into your command-line tool.
As of Xcode 9.3.1, I was able to have the framework added to the command line tool by setting the Mach-O Type to Static Library for the framework. Then in the command line target make sure to add the framework to the Target Dependencies & the Link Binary With Libraries Build Phases. The built executable was then able to run with no issues.
You can use marathon to manage dependencies
https://github.com/JohnSundell/Marathon
This would need more thought if you wanted to distribute app. (You would probably want to install into frameworks folder in that use case.) your mileage may vary with this solution.

Resources