File Handling Tutorial for Server-Side Swift Part 1

In this two-part file handling tutorial, we’ll take a close look at Server-side Swift file handling and distribution by building a MadLibs clone. By Brian Schick.

Leave a rating/review
Download materials
Save for later
Share

In this two-part file-handling tutorial, you’ll take a close look at how Server-Side Swift handles and distributes files. Part One focuses on the internal aspects of file handling: How to persist data to file, and how to access data stored within a server’s file system.

Part Two follows up by exploring best practices for serving up data between a Server-Side Swift app and its consumers.

If you’ve worked with Server-Side Swift, you’ve likely used tools like Vapor’s Fluent or Kitura’s SwiftKuery to integrate databases and other structured data into your app.

But what if you need to interact directly with files and their contents? Do familiar iOS file management tools work as expected on a server and at web scale? You’ll find out in this tutorial.

Your sample project for this file-handling tutorial is a Vapor app called RetroLibs. It’s a nostalgic homage to the classic MadLibs text-based game. You’ll find its starting page brimming with the vintage colors, fonts and styling of Web 1.0.

Start page of the RetroLis app

For this retro game, you’ll use the equally-classic method of reading and writing your story templates directly to and from the file system. You’ll enable basic gameplay by reading and parsing stock templates from disk.

Finally, since RetroLibs is best with your own stories, you’ll accept custom story templates and store them on the file system.

While doing this, you’ll learn both synchronous and asynchronous file read and write techniques.

So go ahead and dive in!

For a quick look at Vapor, see Getting Started with Server-Side Swift with Vapor

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project. Then open Terminal, navigate to the begin folder, and run this command:

open Package.swift

This command opens the project in Xcode. It will take some for Xcode to download the dependencies but when it’s finished, Xcode will populate the schemes and show the dependencies in the project navigator.

As with any Server-Side Swift project, your sample project uses MVC – but with a special twist! Take a look at the three files in your Models directory. Notice anything unusual?

Xcode Model Directory

Your project has no database support! Everything is driven by stored file content. For this reason, your Model objects aren’t classes as usual, but simple Codable structs.

Take your app for a quick spin. In Xcode, ensure you’ve selected the Run schema with My Mac as the target device.

Set Xcode’s working directory: Edit the Run▸My Mac scheme, and check the Run▸Options▸Working Directory checkbox. Click the folder icon to locate your begin folder.

Build and run, then open your browser to localhost:8080. You might see a prompt to allow access to the directory: Be sure to grant access so the app can read and write files.

Documents Folder Access

Note: When you build and run in Xcode, you might get mysterious build errors. Congratulations! You get to swift run in Terminal instead! You can still edit code in the Xcode editor, but you’ll have to vapor build then swift run in Terminal to run the server. You probably won’t see the access prompt, but everything will still work OK.

You’ll see the RetroLibs opening page. Other than proudly displaying its retro colors, it doesn’t do anything yet. It’s time to get to work!

Using Templates

To get underway, you’ll first assemble a list of available RetroLibs templates. You’ll find sample template files in the RetroLibsTemplates subdirectory.

Template Syntax

Take a look at these sample templates. Each template is a simple text file. Templates let users add any number of fill-in-the-blank-with-a-word-of-this-description elements by surrounding inline descriptors with curly braces. For example:

I like {type of food}, because it is {adjective}!

As a bonus(?!) – and in the spirit of the web’s earlier Wild West days — templates can include ad hoc HTML tags. After all, what’s a vintage Web 1.0 page without a bit of freeform inline styling? :]

The <b>{adjective} robot</b> blinked 
its <blink>{adjective} {color} eyes</blink>.

The pre-supplied sample templates are <blink>-free, we promise!

Getting a List of Templates

To keep things simple, each template’s filename is the title you’ll present to users. So you’ll just need to get a list of this directory’s contents.

To get the directory’s contents, you’ll use FileManager, which you likely know from iOS and other Apple-native platforms. Even though FileManager isn’t optimized for server-side use, it’s common to use it to quickly grab the contents of a directory.

Back in Xcode, open FileController.swift, and locate its getTemplateNames() stub method. Replace its contents with the following:

do {
  return try fileManager.contentsOfDirectory(atPath: workingDir)
} catch {
  return nil
}

Here, you leverage two pre-provided static vars. fileManager provides an instance of FileManager.default, while workingDir uses Vapor’s DirectorConfig type to detect the local environment’s working directory, and build a reference to the RetroLibsTemplates directory, which contains your RetroLibs templates.

From there, you simply ask fileManager to return an array of filenames, or nil if there’s an error.

Build and run, then open your browser to localhost:8080 again.

Getting a List of Templates

You’ll now see a list of the sample templates. Nice! But if you click one, you’ll get an error because you haven’t wired up the read-file functionality yet. You’ll also see an unwanted .DS_Store tag-along in the list of templates. You’ll address these issues next.

Reading Template Files

To teach your sample app to read template files, you’ll first add that functionality to FileController.swift. Open it in Xcode, find the static method readFileSync(_:) and replace its contents with:

 
fileManager.contents(atPath: workingDir + filename) 

This uses fileManager again. Note that you wouldn’t want to do this in production. You’ll return to this a bit later to make the read operations production-worthy.

Next, open Models/TemplateCollection.swift, the file tasked with assembling raw template files into an array of structured Template objects. Find its init() method.

Currently, it creates an array of empty Template objects with only a name, but you’ll need it to read each template body from the file system.

To do this, replace the existing for name in templateNames { ... } loop with the following:

for name in templateNames {
  if let data = FileController.readFileSync(name), 
    let content = String(data: data, encoding: .utf8) {
    let (templateParts, tags) = content.toRetroLibsClips()
    let template = Template(name: name, templateParts: templateParts, tags: tags)
    templates.append( template )
  }
}

Here, you use the just-updated readFileSync(_:) to read each template file from disk. You then parse each template’s raw file contents into paired arrays of templateParts, the static parts of a template, and tags, the parts you’ll fill in to create a unique story.