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 3 of 4 of this article. Click here to view the first page.

One-Sided Ranges

To reduce verbosity and improve readability, the standard library can now infer start and end indices using one-sided ranges [SE-0172].

One way this comes in handy is creating a range from an index to the start or end index of a collection:

// Collection Subscript
var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex]
let firstThree = planets[..<4]          // Before: planets[planets.startIndex..<4]

As you can see, one-sided ranges reduce the need to explicitly specify either the start or end index.

Infinite Sequence
They also allow you to define an infinite Sequence when the start index is a countable type:

// Infinite range: 1...infinity
var numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (8, "Neptune")]

planets.append("Pluto")
numberedPlanets = Array(zip(1..., planets))
print(numberedPlanets) // [(1, "Mercury"), (2, "Venus"), ..., (9, "Pluto")]

Pattern Matching
Another great use for one-sided ranges is pattern matching:

// Pattern matching

func temperature(planetNumber: Int) {
  switch planetNumber {
  case ...2: // anything less than or equal to 2
    print("Too hot")
  case 4...: // anything greater than or equal to 4
    print("Too cold")
  default:
    print("Justtttt right")
  }
}

temperature(planetNumber: 3) // Earth

Generic Subscripts

Subscripts are an important part of making data types accessible in an intuative way. To improve their usefulness, subscripts can now be generic [SE-0148]:

struct GenericDictionary<Key: Hashable, Value> {
  private var data: [Key: Value]

  init(data: [Key: Value]) {
    self.data = data
  }

  subscript<T>(key: Key) -> T? {
    return data[key] as? T
  }
}

In this example, the return type is generic. You can then use this generic subscript like so:

// Dictionary of type: [String: Any]
var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1])

// Automatically infers return type without "as? String"
let name: String? = earthData["name"]

// Automatically infers return type without "as? Int"
let population: Int? = earthData["population"]

Not only can the return type be generic, but the actual subscript type can be generic as well:

extension GenericDictionary {
  subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key {
    var values: [Value] = []
    for key in keys {
      if let value = data[key] {
        values.append(value)
      }
    }
    return values
  }
}

// Array subscript value
let nameAndMoons = earthData[["moons", "name"]]        // [1, "Earth"]
// Set subscript value
let nameAndMoons2 = earthData[Set(["moons", "name"])]  // [1, "Earth"]

In this example, you can see that passing in two different Sequence type (Array and Set) results in an array of their respective values.

Miscellaneous

That handles the biggest changes in Swift 4. Now let's go a little more rapidly through some of the smaller bits and pieces.

MutableCollection.swapAt(_:_:)

MutableCollection now has the mutating method swapAt(_:_:) which does just as it sounds; swap the values at the given indices [SE-0173]:

// Very basic bubble sort with an in-place swap
func bubbleSort<T: Comparable>(_ array: [T]) -> [T] {
  var sortedArray = array
  for i in 0..<sortedArray.count - 1 {
    for j in 1..<sortedArray.count {
      if sortedArray[j-1] > sortedArray[j] {
        sortedArray.swapAt(j-1, j) // New MutableCollection method
      }
    }
  }
  return sortedArray
}

bubbleSort([4, 3, 2, 1, 0]) // [0, 1, 2, 3, 4]

Associated Type Constraints

You can now constrain associated types using the where clause [SE-0142]:

protocol MyProtocol {
  associatedtype Element
  associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element
}

Using protocol constraints, many associatedtype declarations could constrain their values directly without having to jump through hoops.

Class and Protocol Existential

A feature that has finally made it to Swift from Objective-C is the ability to define a type that conforms to a class as well as a set of protocols [SE-0156]:

protocol MyProtocol { }
class View { }
class ViewSubclass: View, MyProtocol { }

class MyClass {
  var delegate: (View & MyProtocol)?
}

let myClass = MyClass()
//myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?'
myClass.delegate = ViewSubclass()

Limiting @objc Inference

To expose or your Swift API to Objective-C, you use the @objc compiler attribute. In many cases the Swift compiler inferred this for you. The three main issues with mass inference are:

be inferred isn't obvious

  1. Potential for a significant increase to your binary size
  2. Knowing when @objc will
  3. The increased chance of inadvertently creating an Objective-C selector collisions.

Swift 4 takes a stab at solving this by limiting the inference of @objc [SE-0160]. This means that you'll need to use @objc explicitly in situations where you want the full dynamic dispatch capabilities of Objective-C.

A few examples of where you'll need to make these changes include private methods, dynamic declarations, and any methods of NSObject subclasses.

NSNumber Bridging

There have been many funky behaviors between NSNumber and Swift numbers that have been haunting the language for too long. Lucky for us, Swift 4 squashes those bugs [SE-0170].

Here's an example demonstrating an example of the behavior:

let n = NSNumber(value: 999)
let v = n as? UInt8 // Swift 4: nil, Swift 3: 231

The weird behavior in Swift 3 shows that if the number overflows, it simply starts over from 0. In this example, 999 % 2^8 = 231.

Swift 4 solves the issue by forcing optional casting to return a value only if the number can be safely expressed within the containing type.

Swift Package Manager

There's been a number of updates to the Swift Package Manager over the last few months. Some of the biggest changes include:

  • Sourcing dependencies from a branch or commit hash
  • More control of acceptable package versions
  • Replaces unintuitive pinning commands with a more common resolve pattern
  • Ability to define the Swift version used for compilation
  • Specify the location of source files for each target

These are all big steps towards getting SPM where it needs to be. There's still a long road ahead for the SPM, but it's one that we can all help shape by staying active on the proposals.

For a great overview of what proposals have been recently addressed check out the Swift 4 Package Manager Update.

Eric Cerney

Contributors

Eric Cerney

Author

Erik Kerber

Final Pass Editor

Ray Fix

Team Lead

Over 300 content creators. Join our team.