Early last year, CocoaPods was on the road to a solid, stable 1.0.0 release. Then Apple threw a wrench in the works with Swift and frameworks, and a year later CocoaPods is still playing catch-up. In their talk at GOTO Conference CPH 2015, Marius, the author of most of the CocoaPods frameworks support, and Orta, CocoaPods’s Design Dictator, talk about the process of creating frameworks support for Swift.
Marius Rackwitz and Orta Therox are big contributors to CocoaPods. Marius did all the Swiftification and Frameworkification of CocoaPods (from the old -> new way); Orta is the design dictator, and much more (e.g. runs CocoaDocs, a huge project on it is own; maintains the websites, plug-ins…). They discuss the actual transition for CocoaPods Frameworks and give advice on how you could do it yourself. In this talk, they cover:
- What is CocoaPods?: It is dependency manager, and (more importantly) also a community; it has existed for five years - it is stable, mature.
- Terminology: targets (apps or the extensions that you normally have in your project)
- Build phase: CocoaPods does a few of its own (instead of Xcode)
Integration History (03:36)
Frameworks have not been there forever. Before frameworks, we had static libraries (until i07 they were not available for iOS). You could use them unethically and standard Frameworks with a link, but you could not distribute your own frameworks with your app bundles. We use them for the integration because they were unavailable on OSX and iOS (the only two existing platforms!).
What are Static Libraries? (03:48)
Static libraries are not linked: they are a collection of object files (and object files are compiled with meta information). The machine code is independent from its position. They are just compiled object files.
When we build our source code with Clang, we get the object files and put them into Ranlib (making an archive out of them). And archive has contents (e.g. which object files at which position in the whole file), maintains the file information, and how this object file was originally named. We do this for every architecture. We use Lipo to archive them altogether again to a fat binary.
On the slides we see a library banana kit (has a dependency to the other library, monkey), and a seal eye tool (as our program we want to build, which depends on both of those libraries). Simple program: we just put everything together, compile it and then link it. For CocoaPods, we set up the exclude project and the static library targets. In addition (because static libraries do not contain or support resources, or other associated files), we would need for example, correlator models. CocoaPods has solved that with an additional build files (used for the integrated target), named CocoaPods resources build files. It is nothing different than run script build files: executes a shell script (which lives in a separate shell file, as it is easier to regenerate the separate shell file instead of having them living in the Xcode project in a pods Xcode project; that would cause bigger change to the integration). We needed dynamic frameworks (required for Swift).
Dynamic Frameworks (Required for Swift) (08:37)
Inside a Framework + Cocoa Touch Frameworks (09:37)
Get more development news like this
Framework, if you tab in finder (build product), it is just a file bundle. For example, Alamofire, a very popular networking library (if networking for Swift build):
- The first entry is the executable, belsky, a dynamic library. [Actually, they are static frameworks; you can have a static library at this place and it magically works].
- The headers are automatically on the right, pushing header Swift or for object of seed to interface the Swift parts of the model. The umbrella header is important (together with the model map) because it is responsible for defining the public interface. The umbrella header is referenced in the model map. Everything transitively imported by the umbrella header is part of the public model interface and available for everything linking against it.
- We also see extra documentation information, which is used by another part of the tooling. Those are platform specific (as we see by the file names).
- Info.plist, which contains some meta-information. There is information (e.g. the version and the copyright) in the umbrella header, which is going to rate by default. There is a foundation expert, your library name version and it is a constant. The version information from the info plist file is pulled at build time of the framework, and read. A c file is going to rate it; the constant is not read at wrong time from the info p list, but is part of the binary and is compiled into that. This information are only necessary for import and linking. If you distribute your app for App store builds, they would give more information about what is going on in your libraries and there could be protected information and the framework documentation if you share code across your app and your extension. That is ripped out.
Dynamic Libraries (14:01)
Dynamic libraries are a file linked image (the linker run on it). We compile the source code and then we run the linker over it. We have the Mac object file, which has a header with some meter information (e.g. dependencies), and we repeat the process again for every architecture we need. With Swift files: same, with different artifacts; in the first step we compiled monkey swift and then we exported words in our public interface. Next, we compiled banana kit (making use of foreign symbols: MKMonkey - we have to link that to our monkey swift, the library, and annotate where it comes from). We compile all my program and obviously we cannot export because the program is linked by itself and no one links to a ready program. We have to resolve the symbol c again to both of our libraries.
Dynamic Frameworks have to be embedded in the bundle (frameworks folder). Build files are a copy (named) file. [Marius runs through a demonstration on how that would look like for the pod file]. We needed to come up with a script which allows to embed the pods that are active for the current build configuration (Xcode is not able to allow us a set up). Headers are stripped and the frameworks need to be signed. Everything is signed by itself: frameworks are assigned individually and your other executables assigned individually.
Frameworks vs. Static Libraries (18:44)
- Frameworks are easier to distribute and integrate into the app once they are built and compiled (they bring everything together and allow to bundle resources). They have a separate name scope.
- Reduce the file size if you share the same framework across your app into your extensions.
- Separate frameworks folder in your apex: you can separate frameworks and have two versions of the same framework. You have to separate bundle, which allows you to assess resources by name only from your framework bundle.
- Limit possibilities for optimization, because it is linked at that time when you build it.
- Dead-code stripping is limited with static linking. You can import the object files, you need object files which are not needed or not included.
- Increased load times.
What That Means for CocoaPods (23:19)
For CocoaPods we ended up:
- Getting freebies (e.g. the Clang modules). To pull it off we had to extend the code that you would write when you are using CocoaPods (e.g. use frameworks). We did that initially to make sure that people knew that they were using frameworks (and you have been pitching lately that we should remove it eventually).
- We need integration scenarios; it makes sense to have it in the Podfile end, to have to use it explicitly.
- We had to work around Apple tools. Code signing has been difficult (Apple does not give any notice upfront about what is going to change). We cannot, obviously, deploy ultimately to the App Store in our continuous integration. We rely on user feedback.
Orta: Transitioning (25:30)
I have transitioned three major applications from CocoaPods static libraries to CocoaPods Frameworks. I use plug-ins that nukes CocoaPods from your project. CocoaPods-deintegrate and then I run it. I use this when I am switching between frameworks and libraries and from libraries to frameworks. You have a clean slate when you running pod-install the next time.
In sum: deleting
pods/, changing some of the build phases, and removing any empty Xcode groups that CocoaPods will have made. From there, it is as simple as these three steps: use frameworks in your Podfile, that will dictate that the next time that you do a pod install, it will switch to libraries, to frameworks. Then, run your tests (where you find out where all the failures are).
Common Errors (27:09)
The most common problems are: 1) Pods depending on static libraries (e.g. Google Analytics, Flurry). I have ended up adding app code that links two pods together in order to make that work. The other one is to bug whoever is distributing that library, that maybe they should start supporting frameworks.
2) Libraries expecting resources in the main bundle. They presume that the main app bundle is already inside, which means that you end up with missing images (most of the time).
3) UIFont pods, that we had to dynamically link (we could not even rely on putting font names in our info dot plist).
4) Pods that change behavior using #defines.
Apps with CocoaPods Frameworks (29:00)
Three apps were translated: Eidolon, a Swift app; Energy, two or three days of editing pods and changing the internal structure; Eigen, the most complicated (over 60 pods). We transitioned it and it turned out to be the switch to frameworks. Having 60 pods linked into your app, at launch time it was not working. My eventual fix was to convert it back. As an application, we were not using Swift, we were not using extensions - there were no reasons for us to actively use it.
Help Out + Q&A (32:05)
Q: What is your opinion on Carthage and why is CocoaPods the better dependency model? Marius: This question is difficult to answer (at least the latter part, without taking too much time). I would say both dependency managers are very different in the way they try to achieve their goals and CocoaPods tries to make the whole integration and the whole thing, as transparent as possible to the user. Everything is inspectable and is as you used to have it in Xcode and you would set it up on your own. Even the build process is used as what Xcode is giving you. That makes it easy to ignore Xcode about that, because you do not need to have a working Xcode project to have our working pods spec. If you do not like to have state and maintain information in Xcode projects, pod specs are a better solution for you. Carthage is going a different way at this point. With four platforms, I think we prefer to continue the way we have chosen.
Q: I think you covered this, but they said how manage with float times through the experience with apps being converted? Orta: Yes, the Tumblr app they posted that they said they went from roughly 9.3 seconds to five or six. Which yes, at that point we were…
Q: The takeaway from your session is “you can use frameworks, just do not.” Orta: There is a lot of apps that should be shipping with it I guess the answer is maybe, you are going to have to have a crazy, hybrid solution of some frameworks and some static libraries.
Marius: I feel it depends on your use case and how you use dependencies and how many of them and know about the outside projects that you are using. Dependencies and third party dependencies, which are not actually third party because they maintain open source projects on their own. If you have codes scattered over small libraries, it will definitely have an impact on loading times. But if you have a small amount of big dependencies, you could profit in the loading times (a big executable takes longer to load than a small executable, which has big dependencies).
Orta: Yes, n squares, the more it gets significant, but while it is still small, it is reasonable. We are still shipping some apps the have frameworks turned on, or multiple. But the big ones, no. For the moment.
Q: Nice that you tried to try the Swift version for CocoaPods in your own apps. Do you have a test app that you send off to the App store with every version of CocoaPods? Just to make sure that Apple still accepts what you are doing new? Marius: No, we do not do anything like that because there is just no proper way to do it.
Q: If there is a problem, somebody will call you “hey this just happened where Apple said no”? Marius: Exactly. We try to prevent it by having experienced developers in place for code review in our code team, and using the technology we built on our own, but there will be always edge cases, especially with f extensions and previous stuff like sharing dependencies, on WatchKit extensions and the WatchKit app and having all the stuff in place, it is just not too much as you are really able to test it by one big integration scenario.
Q: As an idea for somebody who has free time on their hands, why not create a server that just checks out their latest version of CocoaPods, builds a project and sends it off to the App Store? Just to make sure it goes through again. Marius: If you can make that work without being able to discard versions from iTunes Connect?
Orta: Because you have no guarantee that Apple are going to…
Marius: Especially to tooling on the rep hand side of things, is going to be problematic, but with the tutorials around fast lane, it could be eventually doable.
Orta: Let’s get Felix. Felix can do it. He will probably just do it in a day if we ask him politely.
Q: One of the standard questions I get when I recommend people to CocoaPods is what should I check in to get, check in just support file, the entire pulse. What is your current… Marius: We can both answer this question. I would say it depends, but I generally prefer having the pods directory in place because that is nice for continuous integration. It does not have to run pod install and you make sure that even on some pods, a force push to master would be run and there is a different version up there, but just new commits for the same version that could help them. We rely on techs in the repository and you cannot change the tech when you do something bad (e.g. a force push). You should not do that but the code could change and you could get something different, and you cannot prevent anything like that because once you have the pods directory in place, it is no need to run CocoaPods at all, or to have CocoaPods even installed. You are much more independent with having the pods directory in place (in some way at least). I know some developers who came up with using GitHub’s models for a pods directory specifically.
Orta: In all of our apps, we do not. In all of our libraries, we do. Libraries, you actually get Carthage support for free (more or less); a lot of the time you will come back to a library after a long amount of time in between. There is also the fact that you cannot entirely rely on other people hosting your code that you will be bringing in; checking in your pods means that you will actively have the code that is necessary for your app every time, in case Twitter decided to take over the Twitter pod and suddenly decided they are not going to support all the old versions of this unofficial Twitter SDK. If you have checked your pods in before hand you would still be able to rely on that code existing in your own fork (in your own repo, effectively). But in general, the applications we work on (where I work), these are permanently developed applications that are constantly going to have development done on them. When a pod disappears, it is very likely we will have found out by the next day and we will have found a mitigation strategy for that problem. It does not have the clutter of it being inside your code reviews, as well as we include our application keys inside our pods directories; we have sensitive data in there too. It is a bunch of yes and no reasons, which is why people ask the question. In my opinion there is no strong answer for one. The multiple paragraphs we have on the guides about it, there are reasons for doing it and reasons for not doing it. You should find our own space for it.
Orta: I want to build a new website on CocoaPods (I do not want to be the one building it - I just want to do all the designing; if someone feels up for building more Ruby websites, that would be cool). I want to build something like medium but for talking about pods. You could say these are the certain pods that I think go well together or they compete. There are many functional reactive programming pods; maybe five or six are worth your time. But all of them have trade-offs and this website should be the way in which people can express those. That is what I would like to see.
Marius: I would like to see some of the easy issues tackled. But also frameworks built for us. We want to still enable configuration dependent pods; would be nice if we could ultimately recognize when no configuration dependent pod is used that we just use the copy files resource files instead and let Xcode do their job (and not our fragile script where we tried to reproduce to do the same because there is no officer to or from Apple that functionality is exposed so that they could reverse that). They have to replicate everything what is done effectively and do it on our own in the shell script. That has shown to be error prone in the past, especially with Apple changing our own things and tweaking stuff. I would like to have the integration as close as possible to what is the way Apple would do it.
Orta: I would like to double that.That is all good and proper,but every time you press build on a CocoaPods project you see this little running script phase. Fixing that problem would remove that problem for everybody. There are roughly 40,000 projects using CocoaPods. If you could do that, you would save maybe a second or so from every single person pressing build, every time. That is a lot of time. And it is totally worth someone’s time to build (But build my website first). Thanks.
About the content
This talk was delivered live in October 2015 at goto; Copenhagen. The video was transcribed by Realm and is published here with the permission of the conference organizers.