iOS build size with Swift 3 and pods - ios

I just noticed cocoapods with Swift increases build size, however when I use same libraries manually then build size is normal.
I created a blank project with some pods and the build size goes to the 10MB and .app file is around 40MB.
I also notices that my .app file contains all my cocoapods frameworks
(around 37MB) by viewing "Package content".
My podfile having these pods
pod 'Alamofire', '~> 4.4'
pod 'SwiftyJSON'
pod 'IQKeyboardManagerSwift'
pod 'ActionSheetPicker-3.0'
pod 'Kingfisher'
pod 'JKNotificationPanel'
My questions are
why my .app file contains all framework, I guess it happens only with Swift and cocoapod (Correct me if I am wrong) ?
How can we reduce the size of build by using cocoapods with Swift
Thanks in advance

If you use the libraries as static libraries, the linker can exclude the parts of them that you don't use from the build. The same is not true for frameworks. So if you just made an app with those libraries and didn't use them, they won't be included at all, so it's not a fair comparison.
When you ask why your app contains all frameworks I assume you mean the ones for the swift runtime and not the dependencies you explicitly asked for in cocoapods. All swift apps have the runtime bundled into them, at least until the runtime becomes stable enough (changes very often nowadays), and then the phone OS will contain a few versions of it and we won't have to include it in the app.
Also, don't get terrified by the app size. The actual size of the app the user downloads is much smaller. You can see it in iTunesConnect's activity tab and then picking your build, once you've uploaded it there obviously. I've seen apps that upload as 120MB or so to iTunesConnect and then the final download to the user is 20 to 30MB.
Edit after getting more info:
You said you are comparing dragging the sources of libraries into your project vs cocoapods, and there's a clear difference here: if you add the source files of the library it's not the same as adding the compiled framework for the library. If you add the sources, a lot of the unused stuff will be optimized out, thus affecting the size. Example of this is when a library includes a category that the library itself is not using. Unless some linker flags are used, the category is optimized out and it doesn't work on the app using the library (you can search for all_load if you want more info). When using frameworks nothing is optimized out, but this is not happening due to Cocoapods. You can use frameworks without Cocoapods, and you should get exactly the same results regarding size.
Edit 2:
It looks like swift 5 might achieve ABI stability! We might be able to choose swift for apps that need to be as small as possible now with it!

Related

iOS Security Question - What are the targets support files and what do they do?

I've been developing iOS apps for about 18 months now, and I am mostly self-taught. I was recently hired and brought onto a very large project with a lot of moving pieces. I'd like to provide a broad outline for the owner of the application who hired me and I also want to do a security check before presenting the outline to him and others, as well as suggest to him that we should ultimately hire an iOS security expert.
Moreover, after a brief overview of the project, I’ve found a few things that stick out to me and I was wondering if they also stick out to anyone viewing this post:
Below the main project file, there is a Xcode project file, named: 'Pods'
Project - Pods
Podfile:
(Lists all pods); will do a separate post for specifics on this).
Frameworks:
(Contains all frameworks from cocoa pods we use).
Pods:
(Contains many folders, each named after each cocoa pods pod we use).
Products:
(List all of the cocoa pods we use, followed by a .framework after each).
Ex: 'Alamofire.framework'
Targets Support Files:
Contains two folders:
1. Pods-UberApp-Uber
2. Pods-UberApp-UberDev
In each of the two folders within 'Targets Support Files' the following files exist (image link below), with the beginning (for the first set of items within the first file) being 'Pods-UberApp-Uber' or (for the second set of items within the second file) 'Pods-UberApp-UberDev':
Screenshot of 'Pods-UberApp-Uber' / 'Pods-UberApp-UberDev' file items, previously described
What are these files and are they present for every Xcode project/iOS app?
Note: Uber is just the demo name of the app for sake of privacy and understanding. Thx.
These files are autogenerated by cocoapods
The only file that you may edit in Pods project, is Podfile. It contains all your dependencies, and may contain some custom settings for configuring them.
If you open Podfile location in the file system, you'll find Podfile.lock nearby: it contains all versions for installed dependencies since the last pod install or pod update. You can try removing Pods folder and run pod install from terminal inside folder with Podfile, it should recreate the Pods directory.
So all these files on your screenshot are just needed by Xcode to build dependencies for you.

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

Xcode Cocoapods Not Finding Framework Header Files

So I am working on an old Xcode project that was developed by a separate developer and am having build errors when running on my Mac due to Cocoapods. The first error is "The sandbox is not in sync with the Podfile.lock”, which I can fix by removing the various Cocoapods files and the old xcworkspace from the project and running pod install in the terminal to create all of this again. The new issue is that some of the classes cannot find the header files from the podfile's listed frameworks.
My theory is because this is an old project and the old developer didn't put version caps on some of the frameworks in the podfile, is that the project is trying to use old methods / classes that are deprecated in the newest updated frameworks. However I am not sure how to get around this as I tried putting version caps on those frameworks in the podfile without any success.
There are two paths:
You investigate each framework, estimate the approximate pod version that could have been used at the time project was active, and use particular version of each pod (you can find info on configuring pod version here
This solution won't work in 90% of cases, however, as iOS is evolving really fast and old frameworks are most probably outdated and not compatible with latest changes.
You investigate each framework and understand what actually changed. You look at headers that each pod has, understand what actually changed and implement changes in your code.
You start with headers, making sure that you include the right ones, and then start migrating code to the newest versions.
Big projects usually have migration guides.
There are no other options. As soon as any project becomes dependant on external code, it's up to developers to ensure that it's up-to-date.

Embedding frameworks inside closed-source Swift framework

Our company wants to distribute a closed-source SDK for iOS to our clients. I've been using Cocoapods to build the framework and built an example app making use of it. Previously the app worked fine on the simulator as well as when deployed on the device. However, I was also embedding the Pods.framework file in the app itself. One other piece of information that may be of interest is that the framework is written in Swift, the included cocoapods dependencies are both Swift and Objective-C.
I wanted to make the pods requirements easier to manage so the user doesn't need to be concerned with them and tried to embed the Pods.framework file inside of the SDK we're building - so I removed the steps to Embed Pods Frameworks and Copy Pods Resources from the example app, leaving them only in the framework, I also removed Pods.framework as a dependency of the example app, leaving it only in the SDK. This seemed to work in the simulator, but the app now crashes on mobile device with dyld: Library not loaded error.
Upon researching it, I stumbled into a few related discussions:
https://github.com/CocoaPods/CocoaPods/issues/344 https://objectpartners.com/2014/06/25/developing-private-in-house-libraries-with-cocoapods/
However, the suggested solution of using private pods does not look like it would work for us, it's my understanding that the source code in the private pod would still be open, and we can't share it with our clients.
Could someone advise on a solution that would work in this case?
OK, I finally have a more durable solution. It's a modified, cleaner version of my old one now that I understand how Xcode links in my Swift sub-frameworks better
Problem that makes distribution/compilation a bit ugly:
Since Swift standard libraries aren't bundled on the device like Obj-C, nor are they guaranteed to be stable between versions yet (stable binary interface promised in Swift 3: https://github.com/apple/swift-evolution#development-major-version--swift-30) we have to make sure the entire project is compiled against the same version of Swift. That means the guy using your closed-source framework has to be using the same version of Swift in their Xcode for their project as you did for compiling the library, even if he's not using Swift in his code because ultimately it's his version of Swift that gets bundled into the app and your SDK runs against. This is only an issue for closed-source frameworks because open-source ones will always be compiled against the same version as final project. Possible workaround is to restrict clients to same version you use or distribute multiple compilations (i.e. Swift 2.1 and Swift 2.0). To remedy this, you could provide users with copies of binaries compiled against multiple versions of Swift.
Aside from that, here is what I had to do during compilation/distribution to make a binary framework that works in Swift:
When building the framework:
In project target, make sure to add Pods.framework to Linked Frameworks and Libraries (make sure this is a pre-compiled RED version of Pods.framework, I had a black compiled Pods.framework in the same directory which built fine but then resulted in a framework that would cause the project to complain about missing armv7 architecture during linker phase in later project)
In Build Settings, under User-Defined section, add a field called BITCODE_GENERATION_MODE and set it to bitcode
DO NOT #import any frameworks in your bridging header, all instructions telling you to do that are leftover from Swift 1.0-1.2 days, you don't need it anymore and it does more harm than good (the later project will complain that it can't find these headers that aren't even exposed to it)
Change build target to Generic iOS Device, Archive and Export the framework
When building the project using the framework:
Drag and drop the framework into the project, in General tab add it to Embedded Binaries and Linked Frameworks and Libraries (you only need to add the framework itself, not the sub-frameworks or the pods file)
In Build Settings tab, add a new path to Framework Search Paths: $(PROJECT_DIR)/MyFramework.framework/Frameworks
Build the project

Podfile usage for iOS apps

I'm very new to using podfiles and have a bit of confusion around how they work and what I need to do to use them. I have followed the 'Getting Started' section on the main website and have successfully created a podfile and run install to pull in the dependencies. The questions I have are as follows:
I see a new section in my project which appears to list the pods i've requested, however they all seem to be in red - is that normal? If not what have I done wrong / what do I need to do?
In terms of some of the Core iOS frameworks (e.g. UIKit, Foundation, etc.) do I still need to manually import them as frameworks into my project? Can I add them to my podfile so I can manage everything in a single place? If so how (does the syntax vary/differ?)?
They appear as red because this frameworks not built yet. After you build project they should look normal.
Yes, CocoaPods only handle third-party dependencies.

Resources