How to modularize common code between apps?

So, I'm starting a 3rd or 4th in house app today. They're all Swift4 using the latest version of Xcode. I have a big group of "Extensions" that I end up copying from project to project. This doesn't really scale in the end. So, starting a new app, I'd like to learn how one decomposes "support" code into a separate project or module or... I'm not even sure anymore.

There was CocoaPods which I was able to use, but never delved into. And then Carthage, same. And I know there's a Swift Package Manager, but I have no idea how that works or where to even go to figure out which parts to learn more about.

What is the basic roadmap (for a novice developer) to extract common code between 2 or more apps and manage that in a separate repository/module/package?

1 Like

Imho there is no compelling solution - in some setups, you may not notice particular flaws, but in general, you'll have to deal with some quirks.

  • Cocoapods
    I never liked it - and it seems that it is already in the state of decay
  • Carthage
    Written in Swift (why use something else?), but big and complicated... and it's not designed for dependencies which evolve alongside the main project
  • SPM
    Is it there yet? ;-)

Some time ago, Xcode nearly had everything I would have needed to use git for simple dependency management, but as of now, it even won't ask you if you want to add a cloned project to an existing one anymore.
I'd still got with the vcs for you use case:
Either using git subtree, git-vendor(1) - manage vendored dependency subtrees or a skeletal base-project that contains all you extensions.
With the latter approach, it's harder to get changes back into the extension-repo, but updates in the apps are as easy as git fetch extensions; git rebase extensions.

In your podfile, you can specify a cocoapods 'development' library.
I've worked with this for quite some time and it works great.

Simply add your extensions library somewhere (i've put it inside my original repository as ../MyApp/MyLibrary , but you can also clone it once and refer to a specific directory) as a repository.

Then, in your podfile specify this like so:
pod 'MyLibrary', :path => "../MyLibrary"

This will appear inside your xcode project (Pods>Development Pods>MyLibrary) and is directly editable, and any code changed there is commited in the MyLibrary repository.

You will need to add a podspec to that library project. There is a lot of content on the internet about setting this up, so if you are going to try this and need more information, let me know and i can elaborate.

So if I'm making an Xcode project to contain my "reusable" extensions, do I create Cocoa Touch Framework, a Cocoa Touch Static Library, or something else (maybe Cross-platform: Empty?)?

Hm. As a newbie, after reading documentation and the forums, I was sure that Swift Package Manager is the way to go.
Isn't it usable for production applications yet? Trying some basic examples and setups worked fine for me.

Unfortunately not; SwiftPM currently does not support graphical applications on iOS/macOS.

It’s a shame, but there aren’t really any excellent dependency management solutions for iOS apps. CocoaPods and Carthage exist - they have their issues, and personally I’m not a great fan of either of them. SwiftPM is supposed to be the first-party solution, but it’s a bit... (what’s the nicest way to say this?)... underdeveloped?

Just wanted to point out that this isn't true at all. CocoaPods development is active and is about to release their next major version. It's just a big project which doesn't release fast.

2 Likes

Both frameworks and static libraries have their pros and cons:

  • Frameworks can carry resources, so if your code has associated resources (strings, images, storyboards, and so on) then that strongly suggests a framework.

  • Frameworks have a runtime cost. A few frameworks is not a problem, but a project with hundreds of frameworks is going to end badly, especially so on iOS.

  • Frameworks allow you to share code between targets. For example, if you have an app and and app extension with a lot of code in common, it makes sense to put that code into a framework.

One other option is to actually add your shared code to each project that uses it. This has all the downsides of a static library and more, in that it can slow down compile time, but it has one big advantage: Simplicity. There’s a significant complexity bump in going from a single app target to an app target with a separate library target.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

5 Likes

As I'm not particularly knowledgable about these things, what are the downsides of static libraries compared to frameworks? Our project currently uses about 15 frameworks (and that's besides cocoapods) and I think it's already slowing app launch significantly. I was hoping to switch to static libraries, but I would like to know what the cons of this approach are.

For one thing, frameworks may contain extra resources beside code. Interfaces, images, JSON data, etc.

I’d suggest following the golden rule of performance: measure first. Make sure it’s really the framework loading that’s slowing your app startup down.

1 Like

The main drawbacks as I see them are:

  • Resources — Static libraries can’t carry resources, so if your framework need resources then you need to find a way to deal with that. If this is your code then you can often just merge the resources into your app and then deal with any conflicts. That’s harder for third-party code.

  • Code Sharing — If you app contains any app extensions, you’ll want to put shared code in a framework so that:

    • You only ship one copy
    • You use less memory use at runtime
  • Tooling — In general the tooling for frameworks is better than the tooling for static libraries. For example, Xcode will automatically create a module for a framework, so you can import it into Swift trivially. Doing that for a static library is more challenging, although my understanding is that the various package management solution out there provide varying degrees of support for this.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

3 Likes

You can measure startup time by using the DYLD_PRINT_STATISTICS environment variable.

Videos:

3 Likes

After using cocoa-pods/carthage for a while across a dozen + projects, I ended up switching to git-submodules when possible.

My general setup is that I have a workspace that includes the app, and any second/third party library projects (usually frameworks) in that workspace.

Pros:

  • Control of and transparency into library sources.
  • No reliance on third-party tooling (outside of git itself, which is basically second party at this point)
  • Option to work on your app and library in the same workspace while having them in separate git repositories
  • If necessary, you can compile the sources directly into your project rather than relying on a framework.
  • Compatible with the recently (currently?) trendy mono-repo pattern.
  • You can deal with source/ABI breaking changes directly, rather than waiting for the library author to update.

Cons:

  • Tooling. Adding a new library is a 3 or 4 step process and you have to manage compilation/dependencies yourself. (As such I tend to use < 5 libraries at a time)
  • The framework/library itself generally is not saved directly in your repository, so others changing/deleting your dependency is a real possibility. (Can be mitigated with your own fork for OSS projects)
  • Frameworks/static libraries still have some annoying complications and/or holes in capabilities.
  • git-submodules are not as well known of feature so some build/git tooling doesn't work well with it. (less of a problem now than it was a couple years ago).

That's my take on it's relevance to my workflow.

Side note: Using gradle on Android made me realize how broken the dependency system is on Apple platforms. With gradle you literally just add a line build.gradle and 99% of the time it just works. As such my first instinct when I want to do something there is to look for a library, and there are both good large scale, and small scale libraries. After having wrestled with library tooling on iOS for years...it's at best my third choice. A simple way to see the difference is the number of square's iOS libraries (7) vs their Android Libraries (15)

I'm really hoping Apple(/SPM) can develop a dependency management solution with a similar level of simplicity for end-users. Personally I think it's the biggest roadblock to having a healthy Swift/Apple development ecosystem and would make a world of difference.

Believe it or not, the best system that’s worked for me so far has been to distill the “support code” into independent .swift files terminalcolors.swift, vectors.swift, fileio.swift, etc and put symlinks to them in the source directories of all the client modules.

2 Likes