New Swift 5.6 features

Unavailability condition (#unavailable)

#available for conditional compilation based on different platforms, versions:

if #available(iOS 15.*) {
  // Wildcard * indicates all Apple platforms. IOS requires >= 15
} else {
  // Below iOS 15
}
Copy the code

Swift 5.6 introduces #unavailable, which means the exact opposite of #available, and the following example expresses the same meaning as the else branch in the above example.

if #unavailable(iOS 15) {
  // Below iOS 15
}
Copy the code

It also supports specifying multiple platforms simultaneously:

if #unavailable(iOS 15, macOS 12) {
  < iOS 15, < macOS 12
}
Copy the code

Note: using #unavailable makes the wildcard unnecessary, our intention is to use it specifically to indicate an environment that is not available, and using * creates ambiguity.

Type placeholders (_, _?)

Swift 5.6 supports the use of placeholders _ or _? Represents the type to declare. We don’t need to explicitly specify the type; the compiler makes its own inference based on the context.

let complexType: [Int: _] = [1: [1].2: [[2]], 3: [(1.2)].4: [{}]]
// The compiler will infer that the type of complexType is [Int: [Any]]

let complexType: [Int: _] = [1: [1].2: [[2]], 3: [(1.2)].4: nil]
// The compiler will infer that the type of complexType is [Int: [Any]?
Copy the code

CodingKeyRepresentable agreement

Let’s start by looking at the following code. We want to code the dictionary. This dictionary is a bit special, and its key is an enumeration type.

enum AnimalType: String.Codable {
  case cat
  case dog
}

struct Animal: Codable {
  var name: String
  var age: String
}

let pets: [AnimalType: Animal] = [
  .cat: .init(name: "Mao", age: "3"),
  .dog: .init(name: "Biu", age: "2")]let petsData = try! JSONEncoder().encode(pets)
print(String(decoding: petsData, as: UTF8.self))
// ["cat",{"name":"Mao","age":"3"},"dog",{"name":"Biu","age":"2"}]
Copy the code

The printed result does not match expectations because Swift does not handle the conversion correctly for key values that are not String/Int. If we pass this encoded string as JSON to the server, we get an error. For this, we had to do extra work to convert the data.

The new Swift 5.6 CodingKeyRepresentable protocol addresses this problem by enabling custom data types for key values. We let the AnimalType follow the protocol and print again as expected:

// {"dog":{"name":"Biu","age":"2"},"cat":{"name":"Mao","age":"3"}}
Copy the code

Any keyword

Any is similar to any, AnyObject, and AnyClass, but their relationship is just like Lei Feng and Leifeng Tower. A word beginning with Any generally refers to the type of erasing type information, and Any is a keyword that describes one very special type: Existential Types.

Existential type is an abstract concept, and if I had to define it, I would describe it this way: protocol as type. For example, the following code,

protocol UIMode {
  var color: Color { get set}}struct LightMode: UIMode {
  var color: Color = .white
}

struct DarkMode: UIMode {
  var color: Color = .black
}

struct ModeManager {
  var mode: UIMode
}
Copy the code

In ModeManager, the protocol UIMode is also called the presence type. After initializing the ModeManager with a DarkMode() instance, we can also use LightMode() to replace the original mode. At compile time, mode is of type UIMode. At runtime, the true type of mode is LightMode, DarkMode, or any other type that follows the protocol, and its value can be distributed dynamically.

We can think of values of an existing type as a box that dynamically holds all values that conform to that protocol type. Values that match the type can be dynamically replaced with each other.

Let’s modify the above code with generics:

struct ModeManager<T: UIMode> {
  var mode: T
}

var manager = ModeManager(mode: DarkMode())
manager.mode = LightMode(a)Copy the code

This code will not compile because when we initialize ModeManager we pass in the DarkMode type. The generics have already constrained mode to DarkMode at the compile level. When we change mode using the LightMode value, The compiler will report an error.

By comparison we can see that when a protocol is a type, it can also be called an existence type. If the protocol is a constraint on generics, it cannot dynamically change its type at run time and the corresponding value can only be distributed statically. Also, a protocol that uses the some keyword in an opaque type cannot use a different type, requiring that we always use the specific type that follows that protocol.

Dynamic is good, but not as efficient as static. The presence type incurs a performance penalty, but it’s the way we write it. So Swift 5.6 introduced the any keyword to alert us to the type of negative impact. In the meantime, we should avoid using any whenever possible.

struct ModeManager {
   var mode: any UIMode
}
Copy the code

Starting with Swift 6, when we use an existence type, the compiler enforces the any keyword flag, otherwise an error will be reported.

As we mentioned earlier, types starting with Any generally erase type information. It has certain dynamic characteristics, but will bring certain performance loss. This rule also applies to SwiftUI, and AnyView in SwiftUI should also be used with caution. However, we cannot generalize, for example, AnyPublisher is still used. Therefore, it is a balanced choice between performance and flexibility to erase type information in exchange for some flexibility.