What’s New in Swift 4?

Learn about what is new in Swift 4. By Eric Cerney.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Private Access Modifier

An element of Swift 3 some haven't been too fond of was the addition of fileprivate. In theory, it's great, but in practice its usage can often be confusing. The goal was to use private within the member itself, and to use fileprivate rarely in situations where you wanted to share access across members within the same file.

The issue is that Swift encourages using extensions to break code into logical groups. Extensions are considered outside of the original member declaration scope, which results in the extensive need for fileprivate.

Swift 4 realizes the original intent by sharing the same access control scope between a type and any extension on said type. This only holds true within the same source file [SE-0169]:

struct SpaceCraft {
  private let warpCode: String

  init(warpCode: String) {
    self.warpCode = warpCode
  }
}

extension SpaceCraft {
  func goToWarpSpeed(warpCode: String) {
    if warpCode == self.warpCode { // Error in Swift 3 unless warpCode is fileprivate
      print("Do it Scotty!")
    }
  }
}

let enterprise = SpaceCraft(warpCode: "KirkIsCool")
//enterprise.warpCode  // error: 'warpCode' is inaccessible due to 'private' protection level
enterprise.goToWarpSpeed(warpCode: "KirkIsCool") // "Do it Scotty!"

This allows you to use fileprivate for its intended purpose rather than as a bandaid to code organization.

API Additions

Now let's take a look at the new shinny features of Swift 4. These changes shouldn't break your existing code as they are simply additive.

Archival and Serialization

Cereal Guy

Up to this point in Swift, to serialize and archive your custom types you'd have to jump through a number of hoops. For class types you'd need to subclass NSObject and implement the NSCoding protocol.

Value types like struct and enum required a number of hacks like creating a sub object that could extend NSObject and NSCoding.

Swift 4 solves this issue by bringing serialization to all three Swift types [SE-0166]:

struct CuriosityLog: Codable {
  enum Discovery: String, Codable {
    case rock, water, martian
  }

  var sol: Int
  var discoveries: [Discovery]
}

// Create a log entry for Mars sol 42
let logSol42 = CuriosityLog(sol: 42, discoveries: [.rock, .rock, .rock, .rock])

In this example you can see that the only thing required to make a Swift type Encodable and Decodable is to implement the Codable protocol. If all properties are Codable, the protocol implementation is automatically generated by the compiler.

To actually encode the object, you'll need to pass it to an encoder. Swift encoders are being actively implemented in Swift 4. Each encodes your objects according to different schemes [SE-0167] (Note: Part of this proposal is still in development):

let jsonEncoder = JSONEncoder() // One currently available encoder

// Encode the data
let jsonData = try jsonEncoder.encode(logSol42)
// Create a String from the data
let jsonString = String(data: jsonData, encoding: .utf8) // "{"sol":42,"discoveries":["rock","rock","rock","rock"]}"

This took an object and automatically encoded it as a JSON object. Make sure to check out the properties JSONEncoder exposes to customize its output.

The last part of the process is to decode the data back into a concrete object:

let jsonDecoder = JSONDecoder() // Pair decoder to JSONEncoder

// Attempt to decode the data to a CuriosityLog object
let decodedLog = try jsonDecoder.decode(CuriosityLog.self, from: jsonData)
decodedLog.sol         // 42
decodedLog.discoveries // [rock, rock, rock, rock]

With Swift 4 encoding/decoding you get the type safety expected in Swift without relying on the overhead and limitations of @objc protocols.

Key-Value Coding

Up to this point you could hold reference to functions without invoking them because functions are closures in Swift. What you couldn't do is hold reference to properties without actually accessing the underlying data held by the property.

A very exciting addition to Swift 4 is the ability to reference key paths on types to get/set the underlying value of an instance [SE-0161]:

struct Lightsaber {
  enum Color {
    case blue, green, red
  }
  let color: Color
}

class ForceUser {
  var name: String
  var lightsaber: Lightsaber
  var master: ForceUser?

  init(name: String, lightsaber: Lightsaber, master: ForceUser? = nil) {
    self.name = name
    self.lightsaber = lightsaber
    self.master = master
  }
}

let sidious = ForceUser(name: "Darth Sidious", lightsaber: Lightsaber(color: .red))
let obiwan = ForceUser(name: "Obi-Wan Kenobi", lightsaber: Lightsaber(color: .blue))
let anakin = ForceUser(name: "Anakin Skywalker", lightsaber: Lightsaber(color: .blue), master: obiwan)

Here you're creating a few instances of force users by setting their name, lightsaber, and master. To create a key path, you simply use a back-slash followed by the property you're interested in:

// Create reference to the ForceUser.name key path
let nameKeyPath = \ForceUser.name

// Access the value from key path on instance
let obiwanName = obiwan[keyPath: nameKeyPath]  // "Obi-Wan Kenobi"

In this instance, you're creating a key path for the name property of ForceUser. You then use this key path by passing it to the new subscript keyPath. This subscript is now available on every type by default.

Here are more examples of ways to use key paths to drill down to sub objects, set properties, and build off key path references:

// Use keypath directly inline and to drill down to sub objects
let anakinSaberColor = anakin[keyPath: \ForceUser.lightsaber.color]  // blue

// Access a property on the object returned by key path
let masterKeyPath = \ForceUser.master
let anakinMasterName = anakin[keyPath: masterKeyPath]?.name  // "Obi-Wan Kenobi"

// Change Anakin to the dark side using key path as a setter
anakin[keyPath: masterKeyPath] = sidious
anakin.master?.name // Darth Sidious

// Note: not currently working, but works in some situations
// Append a key path to an existing path
//let masterNameKeyPath = masterKeyPath.appending(path: \ForceUser.name)
//anakin[keyPath: masterKeyPath] // "Darth Sidious"

The beauty of key paths in Swift is that they are strongly typed! No more of that Objective-C string style mess!

Multi-line String Literals

A very common feature to many programming languages is the ability to create a multi-line string literal. Swift 4 adds this simple but useful syntax by wrapping text within three quotes [SE-0168]:

let star = "⭐️"
let introString = """
  A long time ago in a galaxy far,
  far away....

  You could write multi-lined strings
  without "escaping" single quotes.

  The indentation of the closing quotes
       below deside where the text line
  begins.

  You can even dynamically add values
  from properties: \(star)
  """
print(introString) // prints the string exactly as written above with the value of star

This is extremely useful when building XML/JSON messages or when building long formatted text to display in your UI.

Eric Cerney

Contributors

Eric Cerney

Author

Erik Kerber

Final Pass Editor

Ray Fix

Team Lead

Over 300 content creators. Join our team.