Sharing Swift Code Between iOS and Server Applications
In this tutorial, you’ll learn how share code between iOS and server applications. By Christian Weinberger.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Sharing Swift Code Between iOS and Server Applications
25 mins
- Getting Started
- Why Share Code?
- Exploring the Vapor Project
- Running the Vapor Project
- Spinning up the Database
- Running the Migrations
- Testing the API Using a REST Client
- Exploring the iOS Project
- Running the iOS Project
- Identifying Sharable Components
- Creating a Shared Package in Vapor
- Migrating UserAPIModel
- Migrating CategoryAPIModel
- Migrating AcronymAPIModel
- Integrating With Your Vapor App
- Setting Up a Local Git Repository for Your Library
- Adding the Library to Your Vapor App
- Configuring the API Models
- Integrating With Your iOS App
- Adding the Library to Your iOS App
- Using the Shared API Models
- Cleaning Up
- Where to Go From Here?
Running the iOS Project
To follow this tutorial, you should use the simulator instead of a physical iPhone. Otherwise, you’ll need to connect to your local Vapor server with your physical iOS device.
So now, select an iPhone simulator of your choice and run the project.
The app has three sections in the tab bar that replicate the API controllers from the Vapor app: Acronyms, Users and Categories.
Identifying Sharable Components
Before you set up the projects to use a shared package, think about which components are worth sharing between the Vapor and iOS projects. As the iOS app communicates with the Vapor app to consume its API, this tutorial focuses on sharing the API models:
- API models are the most obvious candidates. This is because Vapor uses them to decode models into JSON data, and iOS uses them to decode JSON data into models.
Other interesting parts you can share between iOS and Vapor that are not covered in this tutorial include:
- API endpoints: Sharing them between the projects reduces the probability of spelling mistakes.
- Errors: Sharing errors and error cases between iOS and Vapor makes error handling easier and also gives you type-safe cases.
- Validations: Sharing validations of forms/models with the iOS app can be useful. It allows the app to present meaningful information about the user input before sending the data to the back end.
- Business logic: Imagine your app has offline functionality. You could share parts of your business logic with the iOS app to enable an offline experience without rewriting the business logic for iOS.
Creating a Shared Package in Vapor
Open a new Terminal window — but don’t close the PostgreSQL instance — and navigate to the starter directory containing the two apps. You’ll create a new library to share with both. In Terminal, enter the following command:
#1
mkdir til-core
cd til-core
#2
swift package init --name TILCore
Here’s what the commands do:
- Make a new directory for the shared library and navigate into it.
- Create a new package using SwiftPM.
The output in Terminal shows you the new files created by SwiftPM. Run the following commands to create the files needed for the shared API models:
#1
mkdir Sources/TILCore/APIModels
#2
touch Sources/TILCore/APIModels/AcronymAPIModel.swift
touch Sources/TILCore/APIModels/UserAPIModel.swift
touch Sources/TILCore/APIModels/CategoryAPIModel.swift
#3
rm Sources/TILCore/TILCore.swift
Here’s what these new commands do:
- Create a new directory to contain the shared models.
- Make the files for the shared models.
- Remove the template file generated by SwiftPM.
Open the library project. In Terminal, enter
open Package.swift
This opens the package in Xcode. Since you deleted the TILCore.swift template file, you have to remove the example test that relies on it. Open TILCoreTests.swift and remove the line that says: XCTAssertEqual(TILCore().text, "Hello, World!")
.
Next, open Package.swift and rename the package name in line 7 from TILCore to til-core:
// ...
let package = Package(
name: "til-core",
// ...
Then, build the TILCore project. It should succeed without errors.
Migrating UserAPIModel
Head back to tilbackend in Xcode and open UserAPIModel.swift. You’ll see:
import Vapor // #1
// #2
public struct UserAPIModel: Content {
public let id: UUID
public let name: String
public let username: String
// #3
public init(id: UUID, name: String, username: String) {
self.id = id
self.name = name
self.username = username
}
// #4
init(user: User) throws {
try self.init(
id: user.requireID(),
name: user.name,
username: user.username
)
}
}
// #5
extension UserAPIModel {
public struct Create: Codable {
public let name: String
public let username: String
public init(name: String, username: String) {
self.name = name
self.username = username
}
// #6
func makeUser() -> User {
User(name: name, username: username)
}
}
}
Here’s what the file does:
- Since the API model is using Vapor’s
Content
protocol, it depends on the Vapor framework. For the shared package, you need to remove any dependencies specific to Vapor or iOS. - This is the definition of the API model with a conformance to
Content
. Vapor requires this conformance so it can use automatic encoding toResponse
as needed by the framework. - This is the initializer.
UserAPIModel
has anid
, aname
and ausername
. - This is a convenience initializer to create
UserAPIModel
fromUser
. - This extension has a struct with all data required to create a new
User
via the API. It’s a good practice to have a separate model for this, as some fields aren’t provided when creating aUser
. For example,id
is only known after the model has been created and stored in the database. - A convenience method to create a
User
entity fromUserAPIModel.Create
.
Now, implement a version of UserAPIModel
for your TILCore library that you can share with your Vapor and iOS apps:
- First, copy and paste the contents of Vapor’s UserAPIModel.swift into your UserAPIModel.swift file in the TILCore library project.
- Then, get rid of the Vapor dependency. Remove
import Vapor
and replace it withimport Foundation
. - Next, replace the conformance to
Content
withCodable
. This allows you to decode and encode the object without the Vapor-specific protocol conformance. - Remove the convenience initializer
init(user:)
(see #4 in the code above) that’s only needed by Vapor and requires aUser
entity. - Last but not least, delete
makeUser()
withinUserAPIModel.Create
.
Your UserAPIModel.swift in the TILCore project will look like this now:
import Foundation
public struct UserAPIModel: Codable {
public let id: UUID
public let name: String
public let username: String
public init(id: UUID, name: String, username: String) {
self.id = id
self.name = name
self.username = username
}
}
extension UserAPIModel {
public struct Create: Codable {
public let name: String
public let username: String
public init(name: String, username: String) {
self.name = name
self.username = username
}
}
}
Migrating CategoryAPIModel
Next, repeat the steps with Vapor’s CategoryAPIModel
:
- First, copy and paste the contents of Vapor’s CategoryAPIModel.swift into your CategoryAPIModel.swift file in the TILCore library project.
- Replace
import Vapor
withimport Foundation
. - Next, replace the conformance to
Content
withCodable
. - Remove the convenience initializer
init(category:)
that’s only needed by Vapor and requires aCategory
entity. - Finally, delete
makeCategory()
withinCategoryAPIModel.Create
.
This is how CategoryAPIModel.swift will look now:
import Foundation
public struct CategoryAPIModel: Codable {
public let id: UUID
public let name: String
public init(id: UUID, name: String) {
self.id = id
self.name = name
}
}
extension CategoryAPIModel {
public struct Create: Codable {
public let name: String
public init(name: String) {
self.name = name
}
}
}