Lessons Learned: Dealing with Swift and CocoaPods
CocoaPods and Swift in an enterprise environment
Hector: Alright, so my backup talk, not nearly as good as the "listing a million things I love about Taylor Swift", is lessons learned over the past eight months working with Capital One- Dealing with Swift and CocoaPods. I love CocoaPods, it's probably the best thing that's happened to iOS since, well, iOS. And we use it all the time, and honestly, it does have its issues much like all of our apps probably do. And I've dealt with some of these issues, I've even brought some of the CocoaPods core contributors to the team to help us out here and there. And I figured over a few months of dealing with these, I figured I have a pretty good talk about it because people are still getting into Swift. People are still getting into bringing in CocoaPods. In fact, I still know of some apps that would rather bring in their dependencies explicitly because CocoaPods didn't support Swift, or maybe they don't quite trust it. And I'm here to tell you that it's very okay to use Swift in CocoaPods.
I'm here to tell you that it's very okay to use Swift in CocoaPods.
01:19:*Hector motions to slide showing code "use_frameworks!". So starting off, first thing is this, most of you all probably already know this, but I'm sure I'm right in saying this is a ruby flag. You put this in your pod file and run a pod install on your project and now you can start using CocoaPods that contain Swift code in them. You can't use pods with Swift in them unless you have this in your pod file. So put it somewhere near the top, you should be fine, but it's definitely the way to go.
The next thing I wanna talk about since- you see use_frameworks!, I guess it's pretty legitimate to start talking about why is it called frameworks?
So I'm gonna talk to you about dynamic versus static. Alright, we have dynamic frameworks in iOS and Cocoa and static libraries. Now, there's some major differences between the two, and also why CocoaPods decided to go with dynamic and embedded frameworks as their implementation for using Swift inside of your CocoaPods as your dependencies. Apple has already told us that we cannot build static libraries that contain Swift code inside. The reason is because the current run time does not ship with the OS, alright, with the Swift standard libraries inside. And in order to actually be able to do that, you would have to copy the Swift standard library inside every single pod every single time, which is going to bloat your app size in the end. So it's not really a good thing, that's why they decided not to do that and that's why they gave us embedded frameworks.
Now, basically what this slide is here to tell you is - don't use static libraries. Alright? We should start moving to embedded frameworks.
03:11: Now, basically what this slide is here to tell you is - don't use static libraries. Alright? We should start moving to embedded frameworks. Start using dynamic frameworks inside of our apps and it will make your life a lot easier. So a little talk about what dynamic frameworks are. They are basically bundles, simple file directories, they are able to actually have resources as distinct files, your images, your story boards and stuff. They can bundle them. When you open them up they look like regular folder and finder. So that being said, you can still use some static libraries inside of your CocoaPods as long as you include them as a resource inside of your pod spec.
If I'm right in saying so I believe there's actually a flag in there called vendored libraries that you can supply a path to your static library and it will be able to actually bring it in. So it's kind of how New Relic works. They actually wrap their static library inside of an embedded framework and that's how you can bring it in as a CocoaPod. You guys could look into that later, it's pretty interesting.
04:27: Now, the next one is, I don't even know how to pronounce that. Lypo, lipo- it really is indeed your friend. One of the problems that we had over the past few months was one of our CocoaPods that we had internally, a private CocoaPod, was it had two static libraries inside of the CocoaPod, and the problem with that was because static libraries are essentially like fat binaries they have different slices for different architectures, and Xcode could not figure out which slice belonged to which static library. There may have been some sort of race condition, we weren't really sure.
...because static libraries are essentially like fat binaries they have different slices for different architectures, and Xcode could not figure out which slice belonged to which static library.
But what we ended up finding out was if we lipoed those two libraries together into one fat binary it actually fixed a couple of the problems that we had. So we weren't actually able to compile, long story short, without actually bringing in these two static libraries and combining them into one fat binary.
So next thing that you wanna know is that there's another flag in your build settings inside of Xcode called, "embedded content contains Swift code." So there have been some issues with CocoaPods where if you have a completely objective-C app and you bring in a CocoaPod that has Swift code inside of it, it would crash at start. I believe it actually compiled but, whether it didn't compile or it crashed at start didn't matter, it just was not usable, alright? And the reason being, like I said before, is because it's a completely Objective-C app, it did not copy the Swift standard library in and because it didn't copy those in, it didn't know where to find it, therefore, whenever you tried to reference your pod with the Swift code inside it would crash 'cause it couldn't find the standard library headers. So if you set this to true inside of your build settings or at least for that pod, it will copy the entire Swift standard library with that pod. So take caution, but if you come across an issue like that, this would be the fix to do it.
Another issue that we came across was using GitHub Enterprise. I don't know how many people in the crowd work for an enterprise who actually have to use GitHub Enterprise, but it's pretty safe to say that most enterprises go through months and months of security checks on the new GitHub Enterprise versions before they allow you to finally use the new GitHub Enterprise. Working for a startup, y'all probably don't have that problem. You probably actually just use regular GitHub. But at Capital One, we use GitHub Enterprise. And because we are really conscious about our security, we wanna make sure that when we upgrade to a new GitHub Enterprise that we're not doing something that could bite us in the butt later.
07:41: So because of that, there is an older GitHub Enterprise version, and y'all may have came across this issue where when you try to do a pod install, it'll hang. It'll get into an infinite loop. It'll just stay there. First day this happened to me, I waited, what, maybe four hours. I came back and I was like, "Oh, this is still not working. This must be a huge pod. I have no idea what's going on." And four hours later, we just stopped it and did not know what was going on. I turned on the verbose flag in pod install and there was something called git clone no-depth --shallow, so it's basically shallow cloning. It allows you to clone the pod without getting the history, makes your pod smaller at least on your computer. And it was just hanging there. And it said HTTPS, Git clone HTTPS pod and we couldn't figure it out for the life of us.
So I said, "You know what? I hate HTTPS, I use SSH, anyways. So what if I change HTTPS to SSH?"
So I said, "You know what? I hate HTTPS, I use SSH, anyways. So what if I change HTTPS to SSH?" And what I mean by that is inside of your pod spec for that pod, you can change the source URL from HTTPS to SSH. I'm sure you all are familiar with that, but that fixed it. And the reason why is- nobody knows. So some people had... There's actually a running... Not a running issue, it's been closed since, but somebody reached out to GitHub and they said, "This is a known problem with an older GitHub Enterprise version where shallow cloning over HTTPS would hang. Nobody understands why. I think I heard somebody once say that it was just an empty implementation, so it just went into an infinite loop or something like that. No idea. But basically for enterprise, try using SSH especially if you're on an older enterprise version which you probably are because most enterprises are very security conscientious, so that's one thing to remember.
10:00: Next one it's fairly obviously, but, angle brackets, we heart, angle brackets, not so much the quotes. So the difference between angle brackets and quote imports and this is what I'm talking about is importing your headers, is quotes will search within the compiled module, whereas angle brackets will search across all compiled modules. You actually allow it to tell it like, "Hey, let's look in this pod and then this header." So this is the proper way to do it and once you move over to using Swift and using frameworks with CocoaPods, all of your pods turn into frameworks. And like I said, frameworks are essentially bundles, they are file directories, alright? Because they are now bundles, it's important to remember that they are now separate from each other, alright? That being said... Remember, embrace the angle brackets.
So speaking of bundles, one of the things that we came across was not really thinking, "What does it mean when we turn our pods into frameworks?" Because we're turning them into bundles before when they were brought in a static libraries before used frameworks came into play, what happened was all of the resources, all of your headers, all of your files, they went into the main bundle of your app. So anytime you wanted to access them you could use single quote brackets 'cause this is within the compiled module and that worked. You can just do UIImage, imageNamed. There you go, it'll find it. But now that we're using use frameworks they are now bundles which means you have to actually query the bundle that the pod is, in order to access it's resources.
12:02: So it's very important now, within your pods, if you have the line and it's bundle.main bundle and you're trying to access a resource or something like that, don't do it. That wasted a lot of our time too. Instead, use in it's bundle, bundle for class. I put self.dynamic type there, or you could put self class if you're still doing in Objective-C. But this will easily grab the bundle that the pod is if you just feed it a class that belongs to that bundle. So easy way to do that and it works very well. And the last thing I wanna talk about is being explicit about your dependencies within your pod specs. So when you're creating a pod, in your pod spec you have to say spec.dependency because sometimes your pods depend on other pods. And before we were able to take advantage of the fact that when you did spec.dependency something or other, if that sub-pod had other pods that you ended up using as sub-pods in the first pod, it's very, very difficult to explain, but basically using recursion, you were able to access a header in a sub-sub-pod somewhere in there.
When we started using frameworks, I don't even know if this has been fixed in CocoaPods, but I do know that this is what fixed it for us. We weren't able to do that, we weren't able to actually access the headers, we weren't able to write the code for sub-sub-pods in a higher parent pod. So what we did was we were more explicit about our dependencies. Inside of our pod specs what we did is said, "Hey, if I knew I'm gonna use code in one pod and I'm gonna use code in that pod's pod's pod, then we were gonna bring it up to the top." We were like, "Hey, I know I have code, that's gonna use Alamofire but Alamofire is already a sub-pod of this private pod that we have. I'm just gonna bring Alamofire as a spec dependency up at the top." And we were a lot more explicit about it and it solved some more of our issues. And that's basically it, does anyone have any questions?
Q1: Hi. I also like CocoaPods. I've got a quick question: If we wanna use frameworks but we have that problem with, for example, Google Analytics being a static library, can you explain again quickly how you wrapped that to make it a framework?
Hector: So there were a couple ways to do it. We actually have a CocoaPods core contributor, which I'm totally gonna plug his name here in, Boris. He can help you with that, but I can tell you probably what you need to do is, and I've seen this done, I can't remember who does it, I think Parse once did it, but you can take your static library put it as a vendored library inside of your pod spec. If you put an empty header, if I'm correct in saying so, the CocoaPods will wrap it as a framework for you.
Q2: Hi, thank you for the great talk. We recently migrated our app from Swift 1.2 to Swift 2 and it was very painful and a really good learning experience.
Hector: I'm aware.
Q2 continuing: But I wonder if we could have done it better. Basically what we did is that we held onto Xcode 6.4 for as much as we could. Then at some point, we just had to migrate. And we not only had to deal with updating pods, but also migrating to Swift 2 and we were unable to find a good way to de-couple that. We weren't able to just move to Swift 2 without dropping support for iOS7 and things like that. Based on your experience dealing with a lot of pods, was there a way or another possible way that we could have incrementally migrate our app to Swift 2 without... It's complicated giving the nature of Swift 2 I guess.
Q2 continuing: But without doing the whole thing all at once? And bringing all of these scary, big changes?
Hector: So that's really just the best way to do it honestly. It's like re-factoring anything. You just have to sit down, you have to grind the metal and just do it. I did that for Swift 1.0 to 1.2, and then 1.2 to 2.0 and I just took a weekend and I just sat down and I got everything there. And as long as your team has a really PR system in place where you have multiple eyes on that looking at each change, and they know what's going on, then that's really gonna be the only way to help. The migrator tool from 1.2 to 2.0 is really good. From 1.0 to 1.2 wasn't so good. But the 2.0 migrator tool is actually very helpful. So I would look into using that just at first. And what I like to do is when it comes up and shows you the diff between what it's about to re-factor, if I see something that I wanna change right there and then, I actually do it. I don't just let it do the out of the box re-factor. I will also do some of my re-factor myself in the diff and then move onto the next file. So this can save you some time from having to go through each file again.