WWDC Quick Look 💓 By SwiftGGTeam
What's new in Swift

What's new in Swift

Watch original video

Highlight

Swift 5.7 brings a lot of new features such as package manager plug-ins, distributed actors, native Regex, simplified generic syntax, C language interoperability improvements, etc., and the docC and Swift.org websites are officially open source.

Core Content

Package Manager Plugin: Automate the build process

There are always some repetitive tasks in your project: generating code, running documentation tools, formatting source code. In the past, these relied on either scripts or Xcode’s Build Phase, which was very scattered to maintain. Swift 5.7 adds two plug-ins to SPM: Command Plugin is like a Makefile target and can be called from the command line; Build Tool Plugin is more powerful and can be automatically executed before/after compilation. At the same time, module aliases are also introduced to solve the problem of module conflicts with the same name.

Concurrency in Swift: Towards Swift 6

Swift 5.7 takes a major step forward in concurrency safety. The compiler can now detect more data race scenarios, including capturing mutable state within closures and data races across tasks. Distributed actor (distributed actor) is the most important new concurrency feature. It extends the actor model from a single process to multiple processes. The syntax for calling a remote actor is exactly the same as calling a local actor.

Detailed Content

Package Manager Plugin: Automate the build process

(07:19) Swift 5.7 adds two plug-ins to SPM. Command Plugin is like a Makefile target that you can call from the command line:

import PackagePlugin

@main struct MyPlugin: CommandPlugin {
    func performCommand(context: PluginContext, arguments: [String]) throws {
        let doccExec = try context.tool(named: "docc").path
        let doccArgs = ["convert", "--output-path", "docs"]
        let process = try Process.run(doccExec, arguments: doccArgs)
        process.waitUntilExit()
    }
}

Key points:

  • CommandPluginTriggered from the command line, suitable for tasks such as document generation and code formatting -context.tool(named:)Get tool paths without hard coding

The Build Tool Plugin is more powerful and can be executed automatically before/after compilation:

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
            )
        ]
    }
}

Key points:

  • BuildToolPluginAutomatically executed during the compilation process -outputFilesDirectoryDeclare the generated files and Xcode will automatically add them to the compilation

(09:23) There is also a long-term pain point: two dependent packages export modules with the same name. Swift 5.7’s module aliases solve this problem:

let package = Package(
    name: "MyStunningApp",
    dependencies: [
        .package(url: "https://.../swift-metrics.git"),
        .package(url: "https://.../swift-log.git")
    ],
    targets: [
        .executableTarget(
            name: "MyStunningApp",
            dependencies: [
                .product(name: "Logging", package: "swift-log"),
                .product(name: "Metrics", package: "swift-metrics",
                         moduleAliases: ["Logging": "MetricsLogging"]),
            ])
    ]
)

The two module names no longer conflict when used:

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

let swiftLogger = Logging.Logger()
let metricsLogger = MetricsLogging.Logger()

Concurrency in Swift: Towards Swift 6

(13:14) Swift 5.7 takes a major step forward in concurrency safety. The compiler can now detect more data race scenarios:

var numbers = [3, 2, 1]

numbers.removeAll(where: { number in
    number == numbers.count  // Compiler warning: mutable state is captured in the closure
})

(14:10) Cross-task data races will also be captured:

var numbers = [3, 2, 1]

Task { numbers.append(0) }  // Modified in another task
numbers.removeLast()         // Compiler warning: data race

(15:54) Distributed actors are one of the most important new concurrency features in Swift 5.7. It extends the actor model from single process to multi-process:

import Distributed

distributed actor Player {
    var ai: PlayerBotAI?
    var gameState: GameState

    distributed func makeMove() -> GameMove {
        return ai.decideNextMove(given: &gameState)
    }
}

Key points:

  • distributed actorDeclare an actor that may be remotely located -distributed funcMark methods that can be called across the network
  • Parameters and return values need to meet serialization requirements (default Codable)

The syntax for calling a remote actor is exactly the same as calling a local actor:

func endOfRound(players: [Player]) async throws {
    for player in players {
        let move = try await player.makeMove()
    }
}

Language simplification: cleaner syntax

(20:12) Swift 5.7 simplifies optional unwrapping. Previously I had to write:

if let mailmapURL = mailmapURL {
    mailmapLines = try String(contentsOf: mailmapURL).split(separator: "\n")
}

If the variable name is short, it will be difficult to read, but if it is long, it will be redundant. Swift 5.7 allows the right side to be omitted:

if let workingDirectoryMailmapURL {
    mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
}

guard let workingDirectoryMailmapURL else { return }
mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")

(21:07) Closure type inference has also been improved. Previously complex closures required explicit type annotation, but now the compiler can infer it from the context:

let entries = mailmapLines.compactMap { line in
    try? parseLine(line)
}

func parseLine(_ line: Substring) throws -> MailmapEntry { }

(22:15) In terms of C language interop, Swift 5.7 improves pointer type conversion:

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)
        }
    }
}

Key points:

  • withMemoryReboundsafely inIntandUInt32Convert pointers between
  • No more manual management of memory layout

Swift Regex: A new way of string processing

(23:41) String parsing has always been a pain point in Swift. Used beforeStringIndexing operations on are verbose and error-prone to write:

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)
}

(25:10) Swift 5.7 introduces native Regex, supporting literal and strongly typed capture:

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)
}

Key points:

  • /.../is a Regex literal, the compiler will check the syntax -match.1andmatch.2is a strongly typed capture, the type isSubstring

(26:34) For more complex scenarios, Regex Builder provides a declarative syntax:

import RegexBuilder

let regex = Regex {
    ZeroOrMore(.horizontalWhitespace)

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

    ZeroOrMore(.horizontalWhitespace)

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

    ZeroOrMore(.horizontalWhitespace)
    ChoiceOf {
        "#"
        Anchor.endOfSubjectBeforeNewline
    }
}

(27:05) Regex components can be reused and can even be embedded in Foundation’s parser:

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/
    }
}

Key points:

  • RegexComponentProtocols allow you to encapsulate common patterns into reusable components -.iso8601.year().month().day()Embed directly into Foundation’s date parser
  • The type of capture isDate, not a string

Simplified generic syntax

(29:02) Swift 5.7 makes writing generic code more natural. Previously, protocols had many restrictions when used as types:

// Before: only generic functions could be used
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map)

// Before: a protocol could not be used directly as a type
func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout Mailmap)  // Compiler error

(31:05) Swift 5.7 introduces explicitanygrammar:

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

Key points:

  • any MailmapMake it clear that this is an existential type
  • Protocols with associated types can also be used asanyType usage

(32:54) The primary associated type makes constraint expression more concise:

// Before
func addEntries1<Entries: Collection, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map)
    where Entries.Element == MailmapEntry

// Swift 5.7
func addEntries1(_ entries: some Collection<MailmapEntry>, to mailmap: inout some Mailmap)

// Or use any
func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap)

Key points:

  • some Collection<MailmapEntry>Equivalent tosome Collection where Element == MailmapEntry
  • anyUsed in scenarios requiring heterogeneous storage,someUsed to hide concrete types

Core Takeaways

1. Automate project workflow with SPM plug-in

Write code generation, document construction, formatting checking and other tasks as SPM plug-ins. The Command Plugin is suitable for CI/CD and local development commands, and the Build Tool Plugin is suitable for code generation. Check which shell scripts in your project can be migrated.

Entrance:PackagePluginframe,CommandPlugin / BuildToolPluginprotocol

2. Replace string parsing code with Regex Builder

All used in the projectString.indexWhere manual parsing is required, it is worth seeing if it can be replaced by Regex or Regex Builder. Especially scenarios such as log parsing, configuration file reading, and data format conversion.

Entrance:RegexBuildermodule,RegexComponentprotocol

3. Prepare for Swift 6’s strict concurrency checks

Turn on the compiler’s Strict Concurrency check and gradually annotate typesSendable. Distributed actors provide a type-safe abstraction for cross-process communication and are suitable for apps that require real-time communication, such as games and collaboration tools.

Entrance:-strict-concurrency=completeSendableprotocol,distributed actor

4. Simplify existing generic code

Check the type-erasure wrapper for handwriting in your project (AnyPublisherAnyCollectionWait for the customized version) and see if you can directly replace it withanygrammar. newsome+ The main association type writing method makes the API signature shorter and clearer.

Entrance:any ProtocolName<AssociatedType>some ProtocolName<AssociatedType>

Comments

GitHub Issues · utterances