Reputation: 6419
Okay, so maybe I'm doing something absolutely wrong so feel free to correct me. Let me explain what I'm trying to achieve.
I work on a project with multiple targets. Each target is used to create a separate app. The targets share a lot of core functionality but some targets add some additional code or only use a minimal set (e. g. some web services) of the "core".
Currently everything is packed into one Xcode project and I use the "Target Membership" functionality (in the File Inspector) to mark which class belongs to which target. Core classes are members of all targets and special classes are only member of one target.
That can easily get confusing and it brings another problem with it. I want to use a CI/CD server to automatically build and publish updates to Test Flight. Since currently all files are in one repository, I cant automatically detect which app needs to be updated.
My plan now is to separate my repository and my project into multiple parts. I thought of using one "Core" projects as well as one project for each app. Each project will reside in it's own GIT repository.
I then create a "master" repository and use GIT subtrees to add the other repositories. I then can setup triggers for each repository separately to build the corresponding apps (or all apps at once if I change something in the core).
The structure is looking good so far but I have a problem with setting up cocoapods correctly. My problem is that I use cocoapod dependencies in my core which is now a Framework. My Podfile currently looks like this:
source 'https://github.com/CocoaPods/Specs.git'
workspace 'Master.xcworkspace'
platform :ios, '10.0'
use_frameworks!
project 'core/Core'
project 'app/MyApp'
def common_pods
pod 'Firebase/Core'
pod 'Alamofire'
end
target 'Core' do
project 'core/Core'
common_pods
target 'CoreTests' do
inherit! :search_paths
end
end
target 'MyApp' do
project 'app/MyApp'
end
And my filesystem looks like this:
If I now import the Core framework in one of the MyApp classes, I get a build error saying "Missing required module 'Firebase'" and if I add "common_pods" to the MyApp target, I can build but at runtime get a bunch of "Class xxx is implemented in both Core.framework and /.../MyApp.app/MyApp. One of the two will be used. Which one is undefined." errors and my app crashes.
I once read that one should make custom frameworks pods and use cocoapods to add them instead of adding them directly via the project targets embed binaries functionality but wouldn't that mean that every time I make a change to the core, I'd need to build the framework and then make a pod update before finally building and running my application? That can't be the solution.
What am I missing? What am I doing wrong? I hope you can help me figure it out.
Upvotes: 2
Views: 1998
Reputation: 8387
The solution is to add pods dependency only in Core and make the imports private, also you need to tweak the generated files after pod install
because pods is simple and adds extra pods into the main target as a dependency if sees that the Xcode framework depends on it.
Steps:
Firebase
in the main app (only in Core) - seems you already do that;import Firebase
in Core as follows:@_implementationOnly
import Firebase
common_pods
into the main app in Podfile
(as you initially set)load 'Podfile.RemoveDuplicates.rb'
post_install do |installer|
remove_duplicated_libraries('Pods-MyApp', 'Pods-Core', installer)
end
Podfile.RemoveDuplicates.rb
def remove_duplicated_libraries(app_pod_name, kit_pod_name, installer)
app_target = installer.aggregate_targets.find { |x| x.name == app_pod_name }
kit_target = installer.aggregate_targets.find { |x| x.name == kit_pod_name }
app_target.xcconfigs.each do |config_name, config_file|
get_libraries_names(kit_target)
.each { |library|
config_file.libraries.delete(library)
config_file.frameworks.delete(library)
}
xcconfig_path = app_target.xcconfig_path(config_name)
config_file.save_as(xcconfig_path)
end
end
def get_libraries_names(pod_target)
libraryNames = pod_target.specs.flat_map do |spec|
frameworkPaths = unless spec.attributes_hash['ios'].nil?
then spec.attributes_hash['ios']['vendored_frameworks']
else spec.attributes_hash['vendored_frameworks']
end || [spec.name.split(/\//, 2).first]
map_paths_to_filenames(frameworkPaths)
end
libraryNames.uniq
end
def map_paths_to_filenames(paths)
Array(paths).map(&:to_s).map do |filename|
extension = File.extname filename
File.basename filename, extension
end
end
Upvotes: 0
Reputation: 151
While splitting my app into frameworks I ran into a similar problem. Every separate target must include its own copies of the pods it uses. Pods are not shared between targets. http://samwize.com/2015/01/26/projects-workspace-embedded-framework-and-cocoapods/
Adding common_pods
to the 'MyApp' target in the podfile as you have tried worked for me, which leads me to believe perhaps your problem is how your framework project was added to your workspace. I followed the instructions here: http://www.splinter.com.au/2016/11/22/scalable-swift/
My understanding is the behavior you are looking for, where linking to the Core framework will also link to the Core framework's pod dependencies, is only possible if you make the Core framework into it's own pod - but as you have noted there is quite a bit of overhead in doing so.
Hopefully Swift Package Manager makes this easier someday :)
Upvotes: 3