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 is
Date, 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 as
anyType 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 == MailmapEntryanyUsed 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=complete,Sendableprotocol,distributed actor
4. Simplify existing generic code
Check the type-erasure wrapper for handwriting in your project (AnyPublisher、AnyCollectionWait 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>
Related Sessions
- Design protocol interfaces in Swift — An in-depth explanation of Swift 5.7
any/someSyntax and same-type requirement - Meet Swift Regex — A complete guide to using Swift Regex, including Unicode handling details
- Meet Swift Async Algorithms — A new algorithm library based on Swift concurrency, including Zip, Merge, Debounce, etc.
- Meet distributed actors in Swift — A hands-on guide to distributed actors, including WebSocket and local network examples
Comments
GitHub Issues · utterances