What’s New in Swift 5.6 and 5.7

What’s New in Swift 5.6 and 5.7

Swift 5.6 was announced in November 2021 and is expected to come out pretty soon. Let’s have a quick round to whats new to the language.

SE-0290: Unavailable keyword

Possibility to write inverted availability conditions by using the new #unavailable keyword. For example, let’s say our application supports a long range of iOS versions that are prior iOS 13 where we had to load the main window manually. So before Swift 5.6 we will do something like:

// if NOT in iOS 13, load the window.
// Post iOS 13 the window is loaded later in the lifecycle, in the SceneDelegate.
guard #available(iOS 13, *) else {
  loadMainWindow()
  return
}

Now with Swift 5.6 we can do things more readable:

if #unavailable(iOS 13, *) {
  loadMainWindow()
}

SE-0315: Type placeholders

“Type placeholders” which directs the compiler to fill in that portion of the type according to the usual type inference rules. Type placeholders are spelled as an underscore (“_"). For example let’s define a dictionary of UIColors, then have the 5.6 Swift compiler infer the type.

let colors: [String: _] = ["red": UIColor.red, "green": UIColor.green]

SE-0320: Improvement for dictionary coding

Up until now the dictionary coding was surprisingly limited in a matter that only Int or String types were allowed as keys. This proposal introduces a whole new protocol named CodingKeyRepresentable witch allows you to have encodable dictionary keys of any type that conform with this protocol. In the below example, I created a custom type ID that conforms with this protocol, which requires providing a codingKey and an init with a generic coding key. Then encoding dictionary that has keys of type ID becomes straightforward.

struct AnyCodingKey: CodingKey {
  var stringValue: String
  var intValue: Int?

  init(stringValue: String) {
    self.stringValue = stringValue
    self.intValue = Int(stringValue)
  }

  init(intValue: Int) {
    self.stringValue = "\(intValue)"
    self.intValue = intValue
  }
}

struct ID: Hashable, Codable, CodingKeyRepresentable {
  static let knownID1 = ID(stringValue: "id_1")
  static let knownID2 = ID(stringValue: "id_2")

  let stringValue: String
  init(stringValue: String) {
    self.stringValue = stringValue
  }

  // MARK: - CodingKeyRepresentable
  var codingKey: CodingKey {
    AnyCodingKey(stringValue: stringValue)
  }

  init?<T>(codingKey: T) where T : CodingKey {
    stringValue = codingKey.stringValue
  }
}

// MARK: - Ussage 
  let data: [ID: String] = [
    .knownID1: "value_1",
    .knownID2: "value_2",
  ]
  let encoder = JSONEncoder()
  do {
    let text = try String(data: encoder.encode(data), encoding: .utf8)
    print("\(text ?? "nil")")
  } catch {}

SE-0337: Supported Concurrency Migration

Swift 5.5 introduced mechanisms to eliminate data races from the language, including the Sendable protocol to indicate which types have values that can safely be used across task and actor boundaries. However, Swift 5.5 doesn’t enforce the use of Sendable and is optional, which means data races can happen. Therefore, this proposal means by default the compiler will check for these possible data races that could happen. For example:

// module A
@preconcurrency func runOnSeparateTask(_ workItem: @Sendable () -> Void)

// module B
import A

class MyCounter {
  var value = 0
}

func doesNotUseConcurrency(counter: MyCounter) {
  runOnSeparateTask {
    counter.value += 1 // no warning, because this code hasn't adopted concurrency
  }
}

func usesConcurrency(counter: MyCounter) async {
  runOnSeparateTask {
    counter.value += 1 // warning: capture of non-Sendable type 'MyCounter'
  }
}

SE-0335: existential any

Swift now allows existential types to be explicitly written with any keyword, creating a syntactic distinction between existential types and protocol conformance constraints. In Swift 5, anywhere that an existential type can be used today, any keyword can be used to explicitly denote an existential type:

protocol P {}
protocol Q {}
struct S: P, Q {}

let p1: P = S() // 'P' in this context is an existential type
let p2: any P = S() // 'any P' is an explicit existential type
let pq1: P & Q = S() // 'P & Q' in this context is an existential type
let pq2: any P & Q = S() // 'any P & Q' is an explicit existential type

In Swift 6, existential types are required be explicitly spelled with any:

protocol P {}
protocol Q {}
struct S: P, Q {}

let p1: P = S() // error
let p2: any P = S() // okay
let pq1: P & Q = S() // error
let pq2: any P & Q = S() // okay

Swift 5.7

Swift 5.7 comes with some small improvements regarding opaque types to improve readability and visibility. Let’s have a quick tour on these changes. SE-0341: Opaque function parameters

Opaque types can now be used in the parameters of functions and subscripts, where they provide a shorthand syntax for the introduction of a generic parameter. This change removes lots of boilerplate code from the function declaration. Before this new come change we find ourself writing generic functions as below:

func horizontal<V1: View, V2: View>(_ v1: V1, _ v2: V2) -> some View {
  HStack {
    v1
    v2
  }
}

This proposal extends the syntax of opaque result types to parameters, allowing one to specify function parameters that are generic without the boilerplate associated with generic parameter lists. The horizontal function above can then be expressed as:

func horizontal(_ v1: some View, _ v2: some View) -> some View {
  HStack {
    v1
    v2
  }
}
view raw

SE-0328: Structural opaque result types

Very similar with the one above now we are allowed to use opaque types as the result type of function, the type of variable, or the result type of subscript. In all cases, the opaque result type must be the entire type. For example:

func getSomeDictionary() -> [some Hashable: some Codable] {
  return [ 1: "One", 2: "Two" ]
}

Did you find this article valuable?

Support Braincuber Technologies by becoming a sponsor. Any amount is appreciated!