Swift 4 lets value models participate in serialization
Codable brings Swift structs and enums into typed serialization. The release also makes string slices explicit and lets modules migrate under separate language modes.
An archival API can influence the shape of an application's model. Foundation's existing NSCoding protocol belongs to Objective-C and excludes Swift structs and enums. A Swift program that prefers those types has to abandon them for archival or introduce a separate representation. Swift 4's serialization work removes that particular reason to reshape the model around classes.
The September 19 release announcement introduces archival and type-safe serialization alongside revised strings and collections. These changes share a concern with representing ordinary data through Swift's type system. Serialization can operate on a value model directly, while string slices acquire a type that exposes their different storage behavior. The compatibility modes let existing modules adopt the compiler before taking every API change.
I find Swift's value types and explicit optional values among its most useful design choices. Serialization that works with those choices is more appealing than a requirement to construct a parallel object model. It keeps the question of how a value is represented externally separate from whether the program should model it as a reference type.
Conformance connects the model to an encoder
SE-0166, by Itai Ferber, Michael LeHew and Tony Parker, introduces Encodable and Decodable. The former defines how a value writes itself to an encoder. The latter defines an initializer that reads from a decoder. Codable is the composition of both protocols, so a type can adopt both directions together or implement only the direction it needs.
Compiler synthesis handles many ordinary models. When the stored properties have the relevant conformances, the compiler can generate coding keys and the corresponding encode or decode implementation. A nested model can therefore participate through the conformances of its members. The proposal demonstrates this with a location struct embedded in a farm struct and an array of raw-valued enum cases.
The following smaller example follows the same mechanism. Its properties already have the required coding support, so the struct does not need handwritten coding methods.
import Foundation
struct Coordinate: Codable {
let latitude: Double
let longitude: Double
}
let original = Coordinate(latitude: 51.5, longitude: -0.1)
let payload = try JSONEncoder().encode(original)
let restored = try JSONDecoder().decode(Coordinate.self, from: payload)
The decoding call names Coordinate.self. On success, the result is a Coordinate, with typed properties available to subsequent code. Failure remains explicit through throwing operations. Adopting Codable does not assert that arbitrary input is valid, or that a malformed document becomes a usable value merely because a target type exists.
This is a different relationship from receiving an unstructured JSON object graph and then asking each dictionary lookup for a usable value. The model tells the decoder which values it needs. Code after successful decoding can use those properties without repeating a cast at each access. Validation of a business rule remains a separate concern if the type's declared properties alone do not express it.
Coding keys describe the external names
The coding design uses semantic key types conforming to CodingKey. A keyed container is parameterized by that key type, so unrelated coding keys cannot be mixed accidentally in the same operation. The compiler can synthesize the key conformance for suitable enums, including string-backed enums used to map property names to external names.
A model can provide a nested CodingKeys enum and still use synthesis when its cases correspond to the properties being encoded or decoded. This allows a deliberate external spelling without changing the model's property names. An external field name is part of the encoded representation, so it need not be identical to the most convenient identifier in the application.
The protocol boundary also permits manual implementations. encode(to:) and init(from:) can replace synthesized behavior when the external representation needs custom handling. Synthesis is therefore a default for suitable structures, not a requirement to expose every implementation detail in a permanent file format.
That distinction matters when source models evolve. A newly renamed property may need to keep its old external key. A model whose internal shape changes may still need to read an existing representation. Codable provides places to implement those decisions, but an automatically synthesized implementation cannot infer an application's compatibility policy for historical data.
Containers separate structure from file format
The protocols define keyed, unkeyed and single-value containers. A keyed container fits a record whose values have semantic names. An unkeyed container describes ordered values without keys. A single-value container handles a representation that consists of one value. These structures let the model describe its encoded shape without making the model itself a JSON parser.
An encoder supplies the format-specific implementation. The release supports external formats such as JSON and property lists, and the proposal separates the conformance protocols from the concrete encoder work. This is why a model adopts Encodable instead of a JSON-specific model protocol. The type provides its coding behavior through the encoder's containers.
The same separation defines a limit. Different formats have different representational capabilities, and encoding can throw when a value is invalid for the chosen format. A conformance establishes participation in the coding system. It does not guarantee that every possible value has a valid representation in every encoder.
Decoding also has to distinguish an absent value from one that has the wrong type. The proposal's keyed-container design includes decodeIfPresent, which returns an optional for a missing or null value while still treating an incompatible encoded value as an error. A contains query can distinguish an absent key from a present null. These operations provide enough information for a custom decoder initializer to apply a deliberate policy for incomplete records.
That policy belongs to the type's representation. Treating a missing field as an acceptable absence is different from treating a malformed number as absent. Collapsing both cases into a default would lose information about invalid input. The container operations let an implementation preserve that distinction instead of requiring every failure to become the same optional result.
I value that combination of a small default and an explicit customization point. A simple struct can avoid repetitive coding methods, while a type with an established representation can write those methods itself. Keeping the model's value semantics does not require accepting an inflexible external schema.
String slices now have a visible storage distinction
The release's String work addresses another place where a familiar value can hide an important representation choice. SE-0163 restores collection conformances to String and introduces Substring as its subsequence type. StringProtocol supports operations shared between strings and substrings.
The proposal explains the storage problem directly. A small substring can retain the entire storage of its original string. Keeping that slice for a long time can therefore preserve a much larger allocation after the rest of the text is no longer needed. A separate Substring type makes the result's intended short- to medium-term use visible.
Converting a substring into a String provides a concrete boundary for longer-term storage. The conversion produces an independent string representation instead of requiring the stored slice to retain the original allocation. Code that processes pieces temporarily can use Substring, while code that keeps a piece can materialize the String it needs.
This fits Swift's broader value model without making storage costs disappear. A value type still has an implementation, and sharing can affect how long storage lives. The explicit slice type communicates that behavior at an API boundary. A generic operation over StringProtocol can accept both forms, while an API requiring a concrete String makes the conversion decision visible.
The compiler and language mode can move separately
Swift 4 ships with Xcode 9 and official Linux binaries for Ubuntu 14.04, 16.04 and 16.10. Its compiler supports the Swift 3.2 compatibility mode and Swift 4.0 mode. In compatibility mode, it accepts the majority of source built by Swift 3.x compilers and hides updates to existing APIs needed to preserve that compatibility. Most new language features remain available there.
Swift 4.0 mode exposes all of the language and API changes, with some migration required for many projects. Xcode and Swift Package Manager manage the -swift-version selection. Modules can migrate individually, so an application need not wait for every dependency to adopt the new source rules before using the compiler.
The package manager also formalizes products and expands control over source layout. On macOS its builds use a sandbox to restrict network access and file-system modification as a mitigation for malicious manifests. These changes affect the route from dependency description to executable code, while Codable and the String changes affect the program's own data representations. Existing modules can keep their compatibility mode during that transition, then adopt the revised APIs where the source changes are understood.