Book Image

Hands-On Full-Stack Development with Swift

By : Ankur Patel
Book Image

Hands-On Full-Stack Development with Swift

By: Ankur Patel

Overview of this book

Making Swift an open-source language enabled it to share code between a native app and a server. Building a scalable and secure server backend opens up new possibilities, such as building an entire application written in one language—Swift. This book gives you a detailed walk-through of tasks such as developing a native shopping list app with Swift and creating a full-stack backend using Vapor (which serves as an API server for the mobile app). You'll also discover how to build a web server to support dynamic web pages in browsers, thereby creating a rich application experience. You’ll begin by planning and then building a native iOS app using Swift. Then, you'll get to grips with building web pages and creating web views of your native app using Vapor. To put things into perspective, you'll learn how to build an entire full-stack web application and an API server for your native mobile app, followed by learning how to deploy the app to the cloud, and add registration and authentication to it. Once you get acquainted with creating applications, you'll build a tvOS version of the shopping list app and explore how easy is it to create an app for a different platform with maximum code shareability. Towards the end, you’ll also learn how to create an entire app for different platforms in Swift, thus enhancing your productivity.
Table of Contents (19 chapters)
Title Page
Copyright and Credits
Dedication
Packt Upsell
Contributors
Preface
Index

Getting started with Swift package manager


Mastering the command line is important, especially when trying to build and deploy Swift on a production Linux machine or in the Cloud. Since Xcode will not be available on those hosts, Apple has provided us with an easy-to-use command-line tool to help create, build, and distribute our Swift code. This tool is called the Swift package manager and it is useful for managing the distribution of Swift code while integrating with the Swift build system to automate the process of downloading, compiling, and linking dependencies. The following are some of the useful commands provided by the package manager to quickly get you started:

  • swift package init: This will create a Swift package or module that is an easy portable way to share code. It will create a package using the name of the folder you are currently in. Passing a --type executable option will make an executable package where the product of the build will be an executable program such as a web server or a command-line program. Think of this as gems for Ruby or node modules for Node.js.
  • swift build: This builds the Swift package you currently are in by compiling Swift code in your Sources folder. If your package is an executable, then it will generate a binary in the .build/debug folder. If you pass a release configuration using the --configuration release option, then it will build a highly optimized binary and place it in .build/release. The same output is generated for non-executable binary but generate Swift modules instead to be imported by whoever wants to use this module.
  • swift run: A quick way to run a Swift executable package from the command line. This command builds the Swift code if it is not built already and runs the binary. You can pass the -c release option to build and run the optimized version of the binary.
  • swift test: To run tests written in the Test folder of your package.
  • swift package generate-xcodeproj: This command generates an Xcode project file so that you can work on the package in Xcode instead of a plain text editor.

These are some of the more important commands that will come in handy when trying to build and test your web server in Swift and also when deploying and running your web application in production. There are a lot more commands and you can learn about them by running swift package in the Terminal:

Building a Swift package

Right now, we will go through an exercise to build a simple Swift package and learn about the important files and folders. We will also publish this package and consume it in another Swift package to show how we can publish packages and import them as dependencies. For our exercise, we will create a simple cat command-line tool which will concatenate and print the contents of the files specify relative to the current directory.

In order for us to do so we will first build a package called FileReader which will read and return the contents of the file. To build this Swift package, we need to do the following:

  1. Create a folder called FileReader (mkdir FileReader) and change directory (cd) into that folder
  2. Run Swift package init and it will generate files and folders for the package

Let's inspect the contents of the package. The following is the file and folder structure inside of FileReader:

~/W/FileReader $ tree .
.
├── Package.swift
├── README.md
├── Sources
│   └── FileReader
│       └── FileReader.swift
├── Tests
   ├── FileReaderTests
   │   └── FileReaderTests.swift
   └── LinuxMain.swift
4 directories, 5 files
  • Package.swift: This file is where you describe meta-information about the package, including dependencies of the package.
  • Sources: This is where you place your Swift code that will get built by the Swift package manager when you run the swift build command. It can contain multiple folders if you want to build multiple products or targets in your package.
  • Tests: This is where you place your test files and that get run when swift test is run from the command line.

Now that we know the basic file and folder structure, we can start writing our Swift code to read files from disk inside of the FileReader.swift file. By default, it will contain boilerplate code which we can remove and replace with this:

import Foundation
class FileReader {
 static func read(fileName: String) -> String? {
   let fileManager = FileManager.default
   let currentDirectoryURL = URL(fileURLWithPath: 
                          fileManager.currentDirectoryPath)
   let fileURL = currentDirectoryURL.appendingPathComponent(fileName)
   return try? String(contentsOf: fileURL, encoding: .utf8)
 }
}

In this file, we import Foundation, which is a standard library available in macOS and Linux and it provides us with the standard library to read from a file path using the FileManager. After that, we define the FileReader class and create one static function in it, called read, that takes a filename and this function will return the contents of the file if the file exists. The code inside the function does the following:

  1. Gets a singleton FileManager object:
   let fileManager = FileManager.default
  1. Creates a URL pointing to the current directory. The current directory is set to the directory from which the OS Process using this library was called from:
   let currentDirectoryURL = URL(fileURLWithPath: fileManager.currentDirectoryPath)
  1. Appends the filename passed to this function to the current directory:
   let fileURL = currentDirectoryURL.appendingPathComponent(fileName)
  1. Tries to read contents of the file if it exists and return it:
   return try? String(contentsOf: fileURL, encoding: .utf8)

Now that we have the code, we can build it using Swift build. To test that our code is working, we need to write a test for it and we can do so by taking the following steps:

  1. Editing the FileReaderTests.swift file and replacing the body of testExample function block with the following:
XCTAssertEqual(FileReader.read(fileName: "hello.txt"), "Hello World")
  1. Running the following command to create a hello.txt file in the root directory of the package with the contents Hello World:
printf "Hello World" > hello.txt
  1. Run the test for your package using the swift test command. You should see the test pass and print as such:
~/W/FileReader $ swift test
Compile Swift Module 'FileReaderTests' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/FileReaderPackageTests.xctest/Contents/MacOS/FileReaderPackageTests
Test Suite 'All tests' started at 2017-09-29 12:14:57.278
Test Suite 'FileReaderPackageTests.xctest' started at 2017-09-29 12:14:57.278
Test Suite 'FileReaderTests' started at 2017-09-29 12:14:57.278
Test Case '-[FileReaderTests.FileReaderTests testExample]' started.
Test Case '-[FileReaderTests.FileReaderTests testExample]' passed (0.094 seconds).
Test Suite 'FileReaderTests' passed at 2017-09-29 12:14:57.372.
Executed 1 test, with 0 failures (0 unexpected) in 0.094 (0.094) seconds
Test Suite 'FileReaderPackageTests.xctest' passed at 2017-09-29 12:14:57.372.
Executed 1 test, with 0 failures (0 unexpected) in 0.094 (0.094) seconds
Test Suite 'All tests' passed at 2017-09-29 12:14:57.372.
Executed 1 test, with 0 failures (0 unexpected) in 0.094 (0.094) seconds

Now that we have a working Swift package, we can publish it.

Publishing a Swift package

Publishing a Swift package is as simple as committing code, tagging it, and pushing it up to a git repository. To publish the package, perform the following steps:

  1. Create a public git repository on github.com.
  2. Open the Terminal and change your directory to your package's path, cd /path/to/your/swift/package. Then initialize the git repository by running the git init command.
  3. Add a remote origin to the local git repo by running this command:
git remote add origin [email protected]:<repoaccount>/<reponame>.git
  1. Make sure to replace the repo account and repo name with the one you created in Step 1.

 

  1. Add all files to this repo using git add . and commit them using git commit -m "Initial Commit".
  2. Tag it with a version. Since it is our first package we will tag it 1.0.0,git tag 1.0.0.
  3. Publish it by pushing it up to the repo along with the tag:
 git push origin master --tags

It is that easy to make a Swift package and publish it. All you need is a git repository to push your code to and tag your code appropriately so that whoever uses your package as a dependency can point to a specific version.

Consuming a Swift package

Next, we will try to use this package to create an executable package called cat that concatenates and prints the contents of the files passed in as arguments to the command. This executable will work like the built-in-system cat command found in most Unix based operating systems. To do so, we need to perform the following steps:

  1. Open the Terminal and create a directory called cat (mkdir cat) and change the directory into it (cd cat).
  2. Initialize the package by running swift package init --type executable. This will generate a main.swift, which is the entry point for the executable and the code will start executing line by line starting from that file.
  3. Add the URL to your GitHub repo that contains the FileReader package and add the following line in your Package.swift under dependencies:
.package(url: "https://github.com/<repoaccount>/<reponame>", from: "1.0.0"),
  1. Add your FileReader package to the dependencies under the targets section in Package.swift:
import PackageDescription

let package = Package(
  name: "cat",
  dependencies: [
    .package(url: "https://github.com/ankurp/FileReader", from: "1.0.0"),
  ],
  targets: [
    .target(
      name: "cat",
      dependencies: ["FileReader"]),
  ]
)
  1. Add the following code to main.swift:
import FileReader

for argument in CommandLine.arguments {
  guard argument != "arg1" else { continue }

  if let fileContents = FileReader.read(fileName: argument) {
    print(fileContents)
  }
}

Let's try to understand what we have done in the preceding code:

  1. Import the FileReader package:
import FileReader
  1. Iterate over the command-line arguments:
for argument in CommandLine.arguments {
  1. We ignore the first argument using the guard clause in Swift because it is the command name cat:
guard argument != "arg1" else { continue }
  1. Print the contents of the file by printing it in the console:
if let fileContents = FileReader.read(fileName: argument) {
  print(fileContents)
}

Now that we have understood the code, let's build and run it to see whether it works. To build and run, just type the following command in the Terminal:

$ swift run cat Package.swift Sources/cat/main.swift

You should see the contents of both the files, Package.swift and Sources/cat/main.swift, printed in the console. Great job! We have a working command line tool written in Swift using one of our published Swift packages:

Installing the package's executable

How do we install the command line tool we just created? Don't worry, it's simple too. All you need to do is build it with the release configuration, so that it builds a highly optimized binary and also add flags to statically link the Swift standard library. This means that the executable can work even when Swift versions change on your operating system, or if you plan on distributing it on another platform, such as Linux. The following is the command to build the executable command with the release configuration:

$ swift build -c release -Xswiftc -static-stdlib

Once you have the binary built, you need to copy it to one of the directories where binaries are stored in your user path. One such place is /usr/local/bin. To copy it, just run the following command and call your binary file whatever you want. In my case, I chose to rename my command to swiftycat:

$ cp -f .build/release/cat /usr/local/bin/swiftycat

Now, try it out in the Terminal by running the following command:

$ swiftycat Package.swift Sources/cat/main.swift