Building a static library with cocoapods - ios

I am trying to build a static library that has different dependencies (AFNetworking for example) specified in a Podfile. I don't want the dependencies to be included in the final static library (call libMyProject.a), I just want to link against them and then create a MyProject.Podspec file where I can put the same dependencies.
The problem is that when I build libMyProject.a the libPods.a is linked and included, so that if I distribute libMyProject.a and other people integrates it in a project which uses some of the same dependencies it will have duplicate symbols issues.
How can I link against the libPods.a lib but not include it in libMyProject.a? It should work just like linking with other existing frameworks.
Thanks!

I solved it by removing the libPods.a lib from the section "Link Binary With Libraries" in Build Phases.

Whilst manually removing the libPods.a from the "Link Binary with Libraries" build phase does indeed work, the real answer is to not let it get added there in the first place.
The reason it is added is because the pod install command is finding the static library target as one of its targets to link with. This could be because it is the first target in the list (cocoapods' implementation causes it to pick the first one if you haven't explicitly specified targets) or it could be because you have explicitly stated it in the 'link_with' section.
The answer I find is to use the link_with section of the Podfile to explicitly state your targets, and omit the static library target.
The pods project is still created, and your dependencies brought into there as you'd expect, but the libPods.a isn't added to the build phase of your static library.
The only problem is what to put into the link_with section, if not your static library. If you have other targets that you do want to link with (an iPhone app target for instance) that's a good choice. But if your only real target is your static library, you need a little workaround.
My successful strategy so far has been to create a static library target (yes, a separate one from your main static library) and call it "Dummy". Specify this target in your Podfile's link_with section.
It is a little distasteful, granted, but it does work.
platform :ios, '5.1.1'
link_with ['Dummy']
pod 'AFNetworking', '= 1.3.1'

Referenced libraries are not (by default) included in the static library product. The linker conflict you're seeing is more likely the result of both your static library and the client app both using the default (implicit) Pod target.
Every Cocoapods-generated target includes a "Pods-target-dummy.m" file that is compiled into the product; if you use the default Pods target, it's just called "Pods-dummy.m". When both the library and client use the default target, the identical symbols produced from compiling the dummy files will cause a link error.
I tried a variation of Craig's answer myself, and found that the link_with statement is also responsible for hooking up the xcconfig generated by Cocoapods, which provides the compiler flags that control the header search path. You can add the xcconfig (or the header search path project settings) manually, of course, but I went looking for a repeatable solution for my team.
My solution is to create an explicit target for the library, with a name that is unlikely to cause conflicts with a client project (for example, the name of the library):
target 'XYZLibrary' do
pod 'AFNetworking', '2.5.2'
...
end
You can include a link_with statement within the target block if the name of the static library target (in your Xcode project) is different, but if there's only one target, I usually prefer to use the same name in both places, making link_with unnecessary.
If you have a unit test target, create two separate targets. (I currently def a set of common pods that are used in both targets, since abstract targets are not currently an option, but they may be one day.) It looks like this:
def common_pods
pod 'AFNetworking', '2.5.2'
end
target 'XYZLibrary' do
common_pods
end
target 'XYZLibraryTests' do
common_pods
end
The key is to not have any pod elements in the root of the Podfile, so that Cocoapods won't generate a default target. That way, each product gets a unique "Pods-target-dummy.m", and there's no conflict when those object files are linked together.

Related

How to change Mach-O Type for a pod using Cocoapods

Task: Change the Mach-O Type for an individual pod (single library).
What changes need to be done in the Podfile so that I can set the Mach-O Type of my choice for any target in the Pods.xcodeproj?
Currently, it is set from the podspec file of that pod. Is there a way I can define this in Podfile?
In my project, I need to use it as a target static library. I can change it manually but, since we're using git and pods folder is excluded from the source code. It gets overridden. Also, we are using CI/CD so, we can not rely on manually changing it every time.
Why not use dynamic? Well, there is one particular library that does not allow the code to generate the code coverage. Changing the Mach-O Type to static solves our problem.

Multiple platform support for Single Target in podfile

Is there any way to add multiple platform support for a single target through podfile?
For example my project is common for both iOS and Mac. They consume the same code base. So instead of creating multiple target for the same code, I added support for both iOS and MacOSX in the same target. It builds fine.
Now I want to add a dependency through Cocoapods. I create a podfile and specify my target's dependency on the pod. The pod in question here supports multiple platform in a similar way i.e. single target.
But now while building my project it fails for iOS.
Specifying multiple platforms in Podfile for single target produces an error.
And if I just specify platform as only iOS or Mac then the project in question fails to build on other platform.
Has anyone experienced this before?
How can I add multiple platform for a single target through podfile?
P.S. - I know I can achieve it by creating multiple targets in my project. But I want to keep that as my last option.
def import_pods
pod 'CorePlot'
end
target 'FirstAppWithMacOS' do
# define your platform here.
platform :ios, '9.0'
import_pods
end
target 'FirstMacOSApp' do
# define your platform here.
platform :osx, '10.10'
import_pods
end
See below image of my project :
The platform device is controlled by Base SDK and Supported Platforms in Architectures section in your target's Build Settings. If you didn't create the separated two targets for both macOS and iOS devices, there should be only one choice left for you to support those platform entries, that is, duplicate new two existing build configurations for macOS platform, then you could configure the settings separately.
For example, you have an existing iOS-based project and there are two default build configurations named Debug and Release. Now try to two new one for macOS, choose the main Xcode project -> choose project name (not target name here) -> select Info section in top -> tap + button under the Configurations section -> choose Duplicate "Debug"/"Release" Configuration -> rename it as Debug-macOS/Release-macOS or others, rename the original Debug/Release to Debug-iOS/Release-iOS, too.
Now you have two pairs of configurations to debug and archive the targeted platforms under the only one target's build settings. You could also create a new scheme for macOS development, just make sure that you choose the right configurations in different actions.
Talking to the main Podfile on cocoapods, if you maintain a cocoapods library by yourself and you want to add it as a dependency, it'd better support both macOS and iOS platform in your podspec file, of course, you need to make compatibility for those platforms, AFNetworking did it like that. Here is the main conversion task, do not use any API under the UIKit for macOS platform. To distinguish the platform difference in compiling time, you could use these macros for that,
#if TARGET_OS_IOS
// iOS supported
#elif TARGET_OS_OSX
// macOS supported
#endif
If you want to use a third-party library in your standalone project target, check the compatible issue first, if it only works for iOS platform, you could specify the dependent library for only one platform like this,
pod 'PonyDebugger', :configurations => ['Debug-iOS', 'Release-iOS']
Same with macOS supported.
Hope it helps!
Short answer
Is is not supported as of CocoaPods 1.10.2. There are reasons why it isn't supported and probably won't be supported any time soon or only when building with Xcode 13.
Long answer
When integrating a pod to a target, CocoaPods (CP) must perform a couple of steps to make that happen. I won't list all of them here but the following four are relevant to this answer:
Create an umbrella header.
Build a (dynamic/static) framework or static library.
Adding the umbrella header to the header search path of the target.
Liking the built framework/library to the target.
Let me go through these steps one by one:
1. Create an umbrella header
The content of the umbrella header depends on the platform the pod is built for. E.g. if it is macOS, it will import Cocoa, if it is iOS, it will import UIKit. As umbrella headers are treated in a special way, especially when using modules (and when dealing with Swift code as well, you do want to use modules, trust me), using #ifdef to make parts of them platform dependent is a rather bad idea. So to support multiple platforms, CP would need to create one umbrella header per platform.
Please note that this is exactly what CP does, if you integrate the same pod into two targets for two different platforms. E.g. if one target is macOS and one is iOS, there will be two umbrella headers, one ending in -macOS.h and one ending in -iOS.h. So in theory, this functionality is there, there is just no way to specify more than one platform per target so far and thus the created umbrella header will only work with one target platform. That is what Vikas Dadheech probably meant when he said:
And if I just specify platform as only iOS or Mac
then the project in question fails to build on other platform.
Currently the created header will only play nice with either platform. Please note that this also holds true for any auto-generated module map file (.modulemap).
2. Build a (dynamic/static) framework or static library
Just as before, how the library/framework is built depends on the platform as a Podspec file can specify different build options per platform and even select different files to be included in a build depending on platform, the pod must be built separately per platform, even if both platforms would use the same CPU instruction set (e.g. ARM Mac and ARM iPad) and the code otherwise contains nothing that is platform specific.
And just as before, this functionality is there when integrating the same pod in into different targets for different platforms, resulting in two libraries/frameworks with a platform suffix.
3. Adding the umbrella header to the header search path of the target
Now its getting a bit more tricky. Assuming we have two umbrella headers (and maybe also two module map files), only one of them must be visible when building the target they integrate into and which one depends the target platform. This is no problem when we are talking about different targets as different build settings per target are easy but in that case it's a single target and this comes with some implications.
Yet even that is solvable, e.g. by making settings depend on SDK, after all you will require a different SDK per platform, so you can do things like this:
HEADER_SEARCH_PATHS[sdk=macosx*] = ....
HEADER_SEARCH_PATHS[sdk=iphoneos*] = ...
to ensure that only the headers and map files for the correct platform are visible for the build.
4. Liking the built framework/library to the target
And here comes the problem: If you want to use Xcode's linking, you can either link against the macOS or the iOS version you've built in step 2 but you cannot link to either one depending on the target platform you are building for. So how do you make sure the binary links against the correct library and only one of them?
You can ...
combine all versions into a universal binary and always link against this one. This will work as long as your target platforms have different CPU architectures (e.g. x86 for Mac and ARM for iOS) but it won't work if your platforms have equal ones (e.g. ARM Mac and ARM iPhone). With the instruction of ARM Macs, this option is pretty much dead and it was already not possible before when your two targets are iOS and tvOS since both have been ARM, too.
not use Xcode's linking at all and instead set linking flags by hand as well as library search paths using the same trick as for the headers above. Well, this will only work with static libraries and frameworks out of the box and only reliably with the new Xcode build system if you manually set target dependencies. With dynamic frameworks you also manually must copy the frameworks, sometimes (re-)sign them correctly, and also manage dependencies by hand. All possible, but seems rather fragile and may have even more implications than I just named above. I guess this is a somewhat risky approach with lots of possible pitfalls and some setups will simply not work that way at all, no matter what you do.
use the selective linking feature of Xcode 13. This is exactly what is needed here and Apple has probably implemented it as the CPU architecture no longer is suitable for many linking cases. Yet Xcode 13 is currently still in beta and if the feature relies upon that capability, it will require using Xcode 13 for building and this requires a Mac running at least Big Sur.
If You want to support multiple platforms in a single target in your Podfile, You can use the platform directive to specify the platforms you want to support. For Example
platform :ios, '9.0'
platform :watchos, '3.0'
Also you can specify a minimum version and a maximum version for each platform. For Example
platform :ios, '9.0'..'14.0'
I did same kind of implementation in my one of project which has 3 targets.
I have created 'def' shared pods and then call this shared_pods in all 3 targets. In this way, It works pretty well. I did only for iOS app.
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'
def shared_pods
pod 'Stripe'
pod 'Google/SignIn'
end
target 'App_Dev' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
shared_pods
end
target 'App_QA' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
shared_pods
end
target 'App_Release' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
shared_pods
end

Custom cocoapods xcodeproj file

By default a CocoaPods installation makes Pods/Pods.xcodeproj. Is there a way I can set it to be called something else, like Pods/MyCustomPods.xcodeproj?
The scenario is that I have a workspace that contains multiple projects in multiple directories, and two of the projects there use CocoaPods. And not only would it then be confusing with two projects named Pods, but Xcode can't cope well with that and confuses the two projects.
Cheers
Nik
The approach with multiple Pods projects was incorrect. In this case, rather make a .xcorkspace and then point your Podfile at it
workspace 'MyCustom.xcworkspace'
then in each of your targets, point at the respective .xcodeproj:
target 'A' do
xcodeproj 'A.xcodeproj'
pod 'SamplePod'
end
target 'B' do
xcodeproj 'B.xcodeproj'
pod 'SamplePod'
pod 'OtherPod'
end
This works really well :-)

ObjC: Statics are not consistent across multiple frameworks when contained in cocoapod

So my situation specifically uses the Objection dependency injection library. However, I believe this could be an issue for other libraries as well.
Platform
iOS 8.0 Xcode 6.1.1. Cocoapods 0.35. Objective-C.
Set up:
I have 4 projects. 3 universal frameworks and a test app (I'm creating an api). The test app does not utilize cocoapods (though some other app might).
All three of my frameworks need to use injection. I created a pod file that looks like
workspace 'workspace.xcworkspace'
platform :ios, '8.0'
xcodeproj 'path/to/proj/one'
xcodeproj 'path/to/proj/two'
xcodeproj 'path/to/proj/three'
target :Project1 do
pod 'Objection', '1.4'
end
target :Project2 do
pod 'Objection', '1.4'
end
target :Project3 do
pod 'Objection', '1.4'
pod 'SDWebImage', '~>3.7.1'
end
Problem
When I use this set up singletons don't work in Objection. However, they only don't work if they (the singleton class) is defined in project 1 and then I call [JSObjection createInjector]; in project 2 (the projects don't make a difference, it is that create is called in a different project to the definition).
Current theory
After battling with this for a while I believe it is due to the warning along the lines of:
Objection is defined in Project 1, Project 2 and Project 3.
Which one will be used undefined.
So when the class registers itself with the injection system (e.g through objection_register_singleton(ClassX)). It is using the JSObjection definition from its project, which might not be the one being used for a class that injects it (e.g. objection_requires_sel(#selector(myClassXObject))).
Question
I want to be able to use the iOS frameworks setup as, from what I understand, it is better overall than static libs. However, I also want to be able to use Cocoapods (and have any app, that uses my api, use Cocoapods).
How do I either a) share one definition across multiple frameworks or b) setup a framework as a Cocoapod (I've seen that this is being developed).
I would love to be able to keep the frameworks linked through the xcode workspace so when one is built the frameworks it depends on are also built. Though having said that, my api will probably become a Cocoapod at some point anyway.
Thanks in advance (and for reading to here)
Indigo

Cocoapods library doesn't correctly return isKindOfClass during unit tests

I have a library that I installed with Cocoapods (ECSlidingViewController). In the code, a comparison is done using -isKindOfClass. In the debugger, printing descriptions and everything says this variable is an ECSlidingViewController. However, when the code is running, it returns NO.
Currently I have my podfile linking the libraries with both the main target and the test target. If I don't do this, the test target can't find the libraries.
This answer solved it for me https://stackoverflow.com/a/27165120/2777364.
In short you should not link pod with your test target and you should create separate linking with at least one other pod for test target to force generating configuration set.
target 'MainTarget' do
pod 'PodYouTryToTest'
end
target 'Tests' do
pod 'AtLeastOneOtherPod'
end
Answer above is "The Right Way" of solving this. As a quick workaround I can propose a method:
Class getBundleDependentClass(Class class) {
return NSClassFromString(NSStringFromClass(class));
}

Resources