Discover how you can add third-party frameworks to your app and keep them up to date using Swift packages in Xcode. We'll show you how to author packages that reference frameworks, explain binary targets and how to specify them in your package manifest file, and demonstrate how to compute checksums so that your clients always get the exact binary you expect.
Frameworks are distributed in the XCFramework format. For further details on creating and versioning an XCFramework, be sure to watch "Binary Frameworks in Swift" from WWDC19.
Hi. My name is Boris, and today I'm going to talk about distributing binary frameworks as Swift packages. In Xcode 11, we introduced support for Swift packages, offering a straightforward approach to distributing libraries as source code. We also introduced XCFrameworks, which provided a better way to distribute closed source binary frameworks and libraries. Now in Xcode 12, we're bringing the advantages of Swift packages for library distribution to closed source libraries as well, with support for binary dependencies. In this video, I'm first going to cover how to use a binary dependency in an app. Next, I'll be talking about how to distribute a binary framework as a Swift package. And finally, we're going to look at how to create a binary framework with Xcode. While many libraries are available in source code form, some developers choose to not make the source code of their library available, and instead, distribute them in binary form. Using such a binary dependency works just like adding a source-based dependency. Let's look at that in a demo.
We'll start with a simple iOS app. You select File, Swift Packages, Add Package Dependency.
We'll pick the BinaryEmoji package...
use version 1.0...
and link the binary with our app.
Now if we look at the project navigator, we'll see a new group, "Referenced Binaries," that contains an XCFramework.
Let's look at that in Finder.
We see that it has a bunch of subdirectories, each of which corresponds to a platform and target environment represented by a target triple.
Inside, you see a corresponding framework for that particular triple.
Let's go back to Xcode.
We can now import the emoji module in our app's code.
And if we jump to "definition," we'll see which API it provides. There's an EmojiView type that I want to use in my app.
If we now refresh the preview...
you see the EmojiView being used. So, using a binary dependency really works the same as a source-based dependency.
If you're already familiar with XCFrameworks from Xcode 11, they are effectively handled the same way as if they were added directly to your app.
If you want to use that same library as a package dependency, it also works the same way you're used to. In the package manifest, you can add an entry to the dependencies array, which points to the repository the package is using and defines the version restrictions that you chose for this dependency.
Now that we saw how to use a binary dependency that someone else authored, next, let's look at how we create such a package that distributes a binary framework ourselves. Xcode 12 offers the new tools-version:5.3 which brings the new package manifest API. This adds a new target type: "binaryTarget." It has a name which corresponds to the module name of the XCFramework...
a HTTPS URL... and a checksum so that the downloaded archive can be verified.
There's also an option to point to an XCFramework by path, that's meant for development. Note that larger XCFrameworks, just as any large binary files, shouldn't be committed to your Git repository, because they will slow down checkouts.
Products can reference binary targets to vend them to clients, just like regular targets. Binary targets use XCFrameworks, as we saw earlier, are only supported on Apple platforms, can be HTTPS or path-based where the path can point to files inside your package. If HTTPS is being used, the binary artifact will be downloaded separately from the Git checkout of your package. This means that you don't pollute the history of your repository with large binaries, and that you can use the same download location for other uses, such as people downloading your framework manually.
The name corresponds to the module name. Names are unique across the graph, so don't bundle other people's binary frameworks. There should be one canonical package for each binary framework. As you evolve the binary targets of your package, you should adhere to semantic versioning, just like you would for source-based targets. For example, breaking changes to your XCFramework, such as renaming a method or type, should lead to an increase of the major version of your package. Similarly, you also want to version your framework itself using the bundle version string setting in the framework's Info.plist. More information about evolving binary frameworks were covered in the WWDC 2019 session "Binary Frameworks in Swift" that I would encourage you to watch. Let's see how we can create our own binary dependency in another demo.
This time we start with a new package using File, New, Swift Package.
We will call it "Emoji." We delete the targets that were added by the template...
and instead, insert a binary target.
The name will also be "Emoji." Use a URL pointing to a local server that has my XCFramework.
And now we need a checksum. How do we generate that? To compute the checksum of a binary framework, use the "swift package compute-checksum" command. This will print the checksum to the terminal, and you can copy it from there and paste it into your package manifest. When your package is being used, Xcode will compute the checksum of the downloaded file and reject it if it does not match the manifest. This ensures that your clients use the exact binary you expect. Now we can insert that checksum and build the package.
And that's how you can bundle an XCFramework into a Swift package for distribution. Finally, let's look at how to create binary frameworks themselves. XCFrameworks were introduced in Xcode 11. They bundle up multiple variants of a framework for different platforms, they support frameworks as well as dynamic and static libraries, and each XCFramework contains a single module. To create an XCFramework, set the "Build Libraries for Distribution" build setting on your existing framework or library target, archive each variant using the "xcodebuild archive" command, and use the "xcodebuild -create-xcframework" command to bundle it up.
There's a lot more detail about this in the WWDC 2019 session "Binary Frameworks in Swift," so I'd recommend watching this before creating binary frameworks with Xcode. Now before we wrap up, I want to talk about the trade-offs of using binary dependencies. You should always carefully consider which third-party components you bring into your projects. With binaries specifically, debugging becomes harder and making your own fixes won't be possible.
You will also be limited to whatever platforms are supported by the author, since you won't be able to rebuild a binary dependency. Keep these points in mind before adding new binary dependencies to your app. To wrap up, you can now distribute existing XCFrameworks as Swift packages. Depending on them works the same way as for source-based packages. Thanks for watching.