Introduction
The Swift Package Manager (SPM) is a powerful tool for managing dependencies in Swift projects. With its straightforward syntax and seamless integration into Xcode, SPM offers a robust and efficient solution for resolving and incorporating external libraries and frameworks. In this blog , we will explore the features of Swift Package Manager, highlighting its simplicity, dependency resolution capabilities, and cross-platform support.
What is Swift Package ?
Swift packages are modular components of code written in Swift/ Objective-C / C, designed to be reusable in various projects. They package together source files, binaries, and resources, simplifying their integration into your app’s project.
What is Swift Package Manager (SPM) ?
The Swift Package Manager is a powerful tool that facilitates the distribution and management of Swift code. Integrated with the Swift build system, it automates the tasks of fetching, compiling, and linking dependencies, streamlining the development process.
Starting from Swift 3.0, the Package Manager comes bundled as an integral part of the Swift programming language, providing developers with a seamless and efficient way to handle package dependencies.
How to expose components/projects through Swift Package ?
To expose any project to outside world, it should contain a package file. For creating a package file, follow the below steps through terminal,
- Navigate to the root folder of the project which you want to expose through package.
- Run this command — swift package init
How does a package file look like?
import PackageDescription
let package = Package(
name: "MyPackage",
platforms: [
.macOS(.v12),
.iOS(.v15),
.watchOS(.v8),
.tvOS(.v15),
],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "MyPackage",
targets: ["MyPackage"]),
],
dependencies: [
// Update Dependency package url here
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "MyPackage",
dependencies: [
//Add dependency Targets Here
]),
.testTarget(
name: "MyPackageTests",
dependencies: ["MyPackage"]),
]
)
In the above structure, MyPackage is the name of package — Ensure your Package name should match with the target name(in xcode workspace) which you are trying to expose here.
The Package object is initialised with various parameters:
- name: The name of the package.
- platforms: An array specifying the target platforms and their versions.
- products: An array of products, in this case, a library product named “MyPackage”.
- dependencies: An array of package dependencies.
- targets: An array of targets in the package. There’s a target named “MyPackage” and a test target named “MyPackageTests”.
Let us now try with one example here, the project name is coreLibrary.
Project structure for this example is
And these are the targets for this project from xcode,
Now, lets try to expose the files inside iOS_Core_Library folder which are added to the target “iOS_Core_Library” through SPM.
Also, this target has a dependency on below subprojects,
‘Alamofire’, ‘5.1.0’
'SwiftEventBus', '5.0.0'
'Nuke', '7.6.3'
'SecureDefaults', '1.1.0'
How to add these dependencies in xcode project ?
Assuming all these third party libraries are already exposed through SPM,
- Open the xcodeproject, select Project → package dependencies and click ‘+’
- Click the search bar at top and enter the name, if xcode is able to find it , it will get listed, else type the entire url
- Choose the appropriate option in dependency rule and click Add Package for the framework target
- Add all the required packages and now you could see all the added packages in project navigator
Now when you want to expose coreLibrary project to outside world through SPM, we should define its dependencies in package file. So, package file for this project should be configured as below,
import PackageDescription
let package = Package(
name: "iOS_Core_Library",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "iOS_Core_Library",
targets: ["iOS_Core_Library"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire", exact: "5.6.4"),
.package(url: "https://github.com/cesarferreira/SwiftEventBus.git", exact: "5.1.0"),
.package(url: "https://github.com/kean/Nuke.git", exact: "7.6.3"),
.package(url: "https://github.com/vpeschenkov/SecureDefaults.git", exact: "1.1.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "iOS_Core_Library",
dependencies: [
"Alamofire",
"SwiftEventBus",
"Nuke",
"SecureDefaults"
],
path: "iOS_Core_Library",
exclude: ["Info.plist"]),
]
)
Here are some of the additional parameters that can be used in package.
- Path –
- Generally, package file tries to expose the file inside this folder — [PackageRoot]/Sources/[TargetName]
- If you want to change this default settings and would like to expose a different folder, then you need to mention the path to the source files through this parameter.
- This should be relative to package root folder
- Exclude –
- To exclude specific file/folder from exposing it.
- This is an array; you can add more than 1 file in this.
- Path –
Note: The package file should be added to the root folder.
How to add this project as dependency for another project through SPM?
For example, if we have a project named, mainApp where this core-library is added as a dependency. Then in mainApp’s package file, dependency should be updated as below,
import PackageDescription
let package = Package(
name: "mainApp",
defaultLocalization: "en",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "mainApp ",
targets: ["mainApp"]),
],
dependencies: [
.package(url: "git@xxxxx.xxxxx.com:xxxx/xxx/ios-core-library.git", branch: "SPM") //update this url field with actual git repo
],
targets: [
.target(
name: "mainApp",
dependencies: [
.product(name: "iOS_Core_Library", package: "ios-core-library")],
path: "mainApp",
exclude: ["Info.plist"]),
]
)
In the above package file, we tried to use code in “SPM” branch of ”iOS_Core_Library” project instead of going for particular Tag version.
Exposing XCframeworks through SPM
In case if we are trying to create this iOS_Core_Library as a xcframework and expose it through SPM, then we can follow below steps.
- Try to host the xcframework created in any git space.
- Create package file for this framework and expose it.
Project structure and package file placement for exposing XCframework should be like this,
And package file should be as below.
import PackageDescription
let package = Package(
name: "iOS_Core_Library",
products: [
.library(
name: "iOS_Core_Library",
targets: ["iOS_Core_Library"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire", exact: "5.6.4"),
.package(url: "https://github.com/cesarferreira/SwiftEventBus.git", exact: "5.1.0"),
.package(url: "https://github.com/kean/Nuke.git", exact: "7.6.3"),
.package(url: "https://github.com/vpeschenkov/SecureDefaults.git", exact: "1.1.0"),
],
targets: [
.binaryTarget(name: "iOS_Core_Library", path: "iOS_Core_Library.xcframework"),
]
)
Final Notes
By embracing Swift Package Manager, developers can enhance their Swift projects by streamlining the development process. The tool ensures a reliable and efficient workflow for managing dependencies, leading to increased productivity and smoother collaboration among team members. With SPM integrated into Xcode, developers can easily add and manage dependencies directly within their Xcode projects, making it a convenient choice for Swift development.