Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2022 EssentialsSwift

WWDC22 · 38 min · Essentials / Swift

What’s new in Swift

Join us for an update on Swift. We’ll take you through performance improvements, explore more secure and extensible Swift packages, and share advancements in Swift concurrency. We’ll also introduce you to Swift Regex, better generics, and other tools built into the language to help you write more flexible & expressive code.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 44 snippets

Command plugins swift · at 7:19 ↗
@main struct MyPlugin: CommandPlugin {

    func performCommand(context: PluginContext, arguments: [String]) throws {
        let process = try Process.run(doccExec, arguments: doccArgs)
        process.waitUntilExit()
    }

}
Build tool plugins swift · at 8:34 ↗
import PackagePlugin

@main struct MyCoolPlugin: BuildToolPlugin {
    func createBuildCommands(context: TargetBuildContext) throws -> [Command] {
        // Run some command
    }
}
Implementing a build tool plugin swift · at 8:39 ↗
import PackagePlugin

@main struct MyCoolPlugin: BuildToolPlugin {
    func createBuildCommands(context: TargetBuildContext) throws -> [Command] {

        let generatedSources = context.pluginWorkDirectory.appending("GeneratedSources")

        return [
            .buildCommand(
                displayName: "Running MyTool",
                executable: try context.tool(named: "mycooltool").path,
                arguments: ["create"],
                outputFilesDirectory: generatedSources)
        ]
    }
}
Module disambiguation with module aliases swift · at 9:23 ↗
let package = Package(
        name: "MyStunningApp",
        dependencies: [
            .package(url: "https://.../swift-metrics.git"),
            .package(url: "https://.../swift-log.git")
        ],
        products: [
            .executable(name: "MyStunningApp", targets: ["MyStunningApp"])
        ],
        targets: [
            .executableTarget(
                name: "MyStunningApp",
                dependencies: [
                    .product(name: "Logging", 
                             package: "swift-log"),
                    .product(name: "Metrics", 
                             package: "swift-metrics",
                             moduleAliases: ["Logging": "MetricsLogging"]),
  ])])
Distinguishing between modules with the same name swift · at 9:42 ↗
// MyStunningApp

import Logging           // from swift-log
import MetricsLogging    // from swift-metrics

let swiftLogger = Logging.Logger()

let metricsLogger = MetricsLogging.Logger()
Example set of protocols swift · at 11:09 ↗
public protocol NonEmptyProtocol: Collection
    where Element == C.Element, 
        Index == C.Index {
    associatedtype C: Collection
}

public protocol MultiPoint {
    associatedtype C: CoordinateSystem
    typealias P = Self.C.P

    associatedtype X: NonEmptyProtocol 
        where X.C: NonEmptyProtocol, 
            X.Element == Self.P
}

public protocol CoordinateSystem {
    associatedtype P: Point where Self.P.C == Self
    associatedtype S: Size where Self.S.C == Self
    associatedtype L: Line where Self.L.C == Self
    associatedtype B: BoundingBox where Self.B.C == Self
}

public protocol Line: MultiPoint {}

public protocol Size {
    associatedtype C: CoordinateSystem where Self.C.S == Self
}

public protocol BoundingBox {
    associatedtype C: CoordinateSystem
    typealias P = Self.C.P
    typealias S = Self.C.S
}

public protocol Point {
    associatedtype C: CoordinateSystem where Self.C.P == Self
}
Memory safety in Swift swift · at 13:14 ↗
var numbers = [3, 2, 1]

numbers.removeAll(where: { number in
    number == numbers.count 
})
Thread safety in Swift swift · at 14:10 ↗
var numbers = [3, 2, 1]

Task { numbers.append(0) } 

numbers.removeLast()
A distributed actor player and a distributed function swift · at 15:54 ↗
distributed actor Player {
   
    var ai: PlayerBotAI?
    var gameState: GameState
    
    distributed func makeMove() -> GameMove {
        return ai.decideNextMove(given: &gameState)
    }
}
A distributed actor call swift · at 16:20 ↗
func endOfRound(players: [Player]) async throws {
    // Have each of the players make their move
    for player in players {
        let move = try await player.makeMove()
    }
}
Optional unwrapping swift · at 20:12 ↗
if let mailmapURL = mailmapURL {

    mailmapLines = try String(contentsOf: mailmapURL).split(separator: "\n")
    
}
Optional unwrapping with long variable names swift · at 20:29 ↗
if let workingDirectoryMailmapURL = workingDirectoryMailmapURL {

    mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
    
}
Cryptic abbreviated variable names swift · at 20:35 ↗
if let wdmu = workingDirectoryMailmapURL {

    mailmapLines = try String(contentsOf: wdmu).split(separator: "\n")
    
}
Unwrapping optionals in Swift 5.7 swift · at 20:46 ↗
if let workingDirectoryMailmapURL {
  
    mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")

}

guard let workingDirectoryMailmapURL else { return }

mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
Closure type inference swift · at 21:07 ↗
let entries = mailmapLines.compactMap { line in

    try? parseLine(line)

}

func parseLine(_ line: Substring) throws -> MailmapEntry {  }
Type inference for complicated closures swift · at 21:33 ↗
let entries = mailmapLines.compactMap { line in
    do {        
        return try parseLine(line)
    }
    catch {
        logger.warn("Mailmap error: \(error)")
        return nil
    }
}


func parseLine(_ line: Substring) throws -> MailmapEntry {  }
Mismatches that are harmless in C... swift · at 22:15 ↗
// Mismatches that are harmless in C…
int mailmap_get_size(mailmap_t *map);
void mailmap_truncate(mailmap_t *map, unsigned *sizeInOut);

void remove_duplicates(mailmap_t *map) {
    int size = mailmap_get_size(map);
    size -= move_duplicates_to_end(map);
    mailmap_truncate(map, &size);
}


// …cause problems in Swift.
func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) {
    var size = mailmap_get_size(map)
    size -= moveDuplicatesToEnd(map)
    mailmap_truncate(map, &size)
}
Better interoperability with C-family code swift · at 22:33 ↗
func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) {
    var size = mailmap_get_size(map)
    size -= moveDuplicatesToEnd(map)
    withUnsafeMutablePointer(to: &size) { signedSizePtr in
        signedSizePtr.withMemoryRebound(to: UInt32.self, capacity: 1) { unsignedSizePtr in
            mailmap_truncate(map, unsignedSizePtr)
        }
    }
}
String parsing is hard swift · at 23:41 ↗
func parseLine(_ line: Substring) throws -> MailmapEntry {
    func trim(_ str: Substring) -> Substring {
        String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...]
    }

    let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)])
    guard let nameEnd = activeLine.firstIndex(of: "<"),
          let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"),
          trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else {
        throw MailmapError.badLine
    }

    let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd])
    let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd]

    return MailmapEntry(name: name, email: email)
}
String parsing is still hard with better indexing swift · at 24:05 ↗
func parseLine(_ line: Substring) throws -> MailmapEntry {
    func trim(_ str: Substring) -> Substring {
        String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...]
    }

    let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)])
    guard let nameEnd = activeLine.firstIndex(of: "<"),
          let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"),
          trim(activeLine[(emailEnd + 1)...]).isEmpty else {
        throw MailmapError.badLine
    }

    let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd])
    let email = activeLine[(nameEnd + 1)..<emailEnd]

    return MailmapEntry(name: name, email: email)
}
What's the problem? swift · at 24:20 ↗
let line = "Becca Royal-Gordon <[email protected]>       # Comment"

func parseLine(_ line: Substring) throws -> MailmapEntry {
    func trim(_ str: Substring) -> Substring {
        String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...]
    }

    let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)])
    guard let nameEnd = activeLine.firstIndex(of: "<"),
          let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"),
          trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else {
        throw MailmapError.badLine
    }

    let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd])
    let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd]

    return MailmapEntry(name: name, email: email)
}
Drawing a picture swift · at 24:55 ↗
"Becca Royal-Gordon <[email protected]>       # Comment"

/  space name space <      email      > space # or EOL /
/  \h* ( [^<#]+? )?? \h* < ( [^>#]+ ) > \h* (?: #|\Z)  /
Swift Regex using a literal swift · at 25:10 ↗
func parseLine(_ line: Substring) throws -> MailmapEntry {

    let regex = /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/

    guard let match = line.prefixMatch(of: regex) else {
        throw MailmapError.badLine
    }

    return MailmapEntry(name: match.1, email: match.2)
}
Did a cat walk across your keyboard? swift · at 25:46 ↗
/\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/
Regex builder swift · at 26:34 ↗
import RegexBuilder

let regex = Regex {
    ZeroOrMore(.horizontalWhitespace)
  
    Optionally {
        Capture(OneOrMore(.noneOf("<#")))
    }
        .repetitionBehavior(.reluctant)

    ZeroOrMore(.horizontalWhitespace)

    "<"
    Capture(OneOrMore(.noneOf(">#")))
    ">"

    ZeroOrMore(.horizontalWhitespace)
    ChoiceOf {
       "#"
       Anchor.endOfSubjectBeforeNewline
    }
}
Turn a regex into a reusable component swift · at 27:05 ↗
struct MailmapLine: RegexComponent {
    @RegexComponentBuilder
    var regex: Regex<(Substring, Substring?, Substring)> {
        ZeroOrMore(.horizontalWhitespace)

        Optionally {
            Capture(OneOrMore(.noneOf("<#")))
        }
            .repetitionBehavior(.reluctant)

        ZeroOrMore(.horizontalWhitespace)

        "<"
        Capture(OneOrMore(.noneOf(">#")))
        ">"

        ZeroOrMore(.horizontalWhitespace)
        ChoiceOf {
           "#"
            Anchor.endOfSubjectBeforeNewline
        }
    }
}
Use regex literals within a builder swift · at 27:30 ↗
struct MailmapLine: RegexComponent {
    @RegexComponentBuilder
    var regex: Regex<(Substring, Substring?, Substring)> {
        ZeroOrMore(.horizontalWhitespace)

        Optionally {
            Capture(OneOrMore(.noneOf("<#")))
        }
            .repetitionBehavior(.reluctant)

        ZeroOrMore(.horizontalWhitespace)

        "<" 
        Capture(OneOrMore(.noneOf(">#")))
        ">" 

        ZeroOrMore(.horizontalWhitespace)
        /#|\Z/
   }
}
Use Date parsers within Regex builders swift · at 27:39 ↗
struct DatedMailmapLine: RegexComponent {
    @RegexComponentBuilder
    var regex: Regex<(Substring, Substring?, Substring, Date)> {
        ZeroOrMore(.horizontalWhitespace)

        Optionally {
            Capture(OneOrMore(.noneOf("<#")))
        }
            .repetitionBehavior(.reluctant)

        ZeroOrMore(.horizontalWhitespace)

        "<" 
        Capture(OneOrMore(.noneOf(">#")))
        ">" 

        ZeroOrMore(.horizontalWhitespace)

        Capture(.iso8601.year().month().day())

        ZeroOrMore(.horizontalWhitespace)
        /#|\Z/
   }
}
Matching methods and strongly type captures in Regex swift · at 27:49 ↗
func parseLine(_ line: Substring) throws -> MailmapEntry {

    let regex = /\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/
    // or let regex = MailmapLine()

    guard let match = line.prefixMatch(of: regex) else {
        throw MailmapError.badLine
    }

    return MailmapEntry(name: match.1, email: match.2)
}
A use case for protocols swift · at 29:02 ↗
/// Used in the commit list UI
struct HashedMailmap {
    var replacementNames: [String: String] = [:]
}

/// Used in the mailmap editor UI
struct OrderedMailmap {
    var entries: [MailmapEntry] = []
}

protocol Mailmap {
    mutating func addEntry(_ entry: MailmapEntry)
}

extension HashedMailmap: Mailmap {  }
extension OrderedMailmap: Mailmap {  }
Using the Mailmap protocol swift · at 29:26 ↗
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}
`Mailmap` and `any Mailmap` swift · at 31:05 ↗
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}
Improvements to `any` types swift · at 31:17 ↗
extension Mailmap {
    mutating func mergeEntries<Other: Mailmap>(from other: Other) {  }
}

func mergeMailmaps(_ a: any Mailmap, _ b: any Mailmap) -> any Mailmap {
    var copy = a
    copy.mergeEntries(from: b)
    return a
}
More improvements to `any` types swift · at 32:21 ↗
protocol Mailmap: Equatable {
    mutating func addEntry(_ entry: MailmapEntry)
}

func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}
Using Collection as an `any` type swift · at 32:54 ↗
protocol Mailmap: Equatable {
    mutating func addEntry(_ entry: MailmapEntry)
}

func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}
Primary associated types swift · at 33:04 ↗
protocol Collection<Element>: Sequence {
    associatedtype Index: Comparable
    associatedtype Iterator: IteratorProtocol<Element>
    associatedtype SubSequence: Collection<Element>
                                    where SubSequence.Index == Index,
                                          SubSequence.SubSequence == SubSequence

    associatedtype Element
}
Using primary associated types in Collection swift · at 33:42 ↗
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

extension Collection<MailmapEntry> {  }
Example of type erasing wrappers swift · at 34:35 ↗
struct AnySprocket: Sprocket {
    private class Base {  }
    private class Box<S: Sprocket>: Base {  }
    private var box: Base

    // …dozens of lines of code you hate
    // having to maintain…
}
Replace boxes with built-in `any` types swift · at 34:38 ↗
struct AnySprocket: Sprocket {
    private var box: any Sprocket

    // …fewer lines of code you hate
    // having to maintain…
}
Or try type aliases swift · at 34:44 ↗
typealias AnySprocket = any Sprocket
`any` types have important limitations swift · at 35:09 ↗
protocol Mailmap: Equatable {
    mutating func addEntry(_ entry: MailmapEntry)
}

func areMailmapsIdentical(_ a: any Mailmap, _ b: any Mailmap) -> Bool {
    return a == b
}
Using generic types vs. `any` types swift · at 35:44 ↗
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}
`some Mailmap` and `any Mailmap` swift · at 36:40 ↗
func addEntries1<Entries: Collection<MailmapEntry>>(_ entries: Entries, to mailmap: inout some Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}
`some Mailmap` and `any Mailmap` with Collection and primary associated types swift · at 36:50 ↗
func addEntries1(_ entries: some Collection<MailmapEntry>, to mailmap: inout some Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) {
    for entry in entries {
        mailmap.addEntry(entry)
    }
}

Resources