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 ↗Code shown on screen · 44 snippets
Command plugins
@main struct MyPlugin: CommandPlugin {
func performCommand(context: PluginContext, arguments: [String]) throws {
let process = try Process.run(doccExec, arguments: doccArgs)
process.waitUntilExit()
}
} Build tool plugins
import PackagePlugin
@main struct MyCoolPlugin: BuildToolPlugin {
func createBuildCommands(context: TargetBuildContext) throws -> [Command] {
// Run some command
}
} Implementing a build tool plugin
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
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
// MyStunningApp
import Logging // from swift-log
import MetricsLogging // from swift-metrics
let swiftLogger = Logging.Logger()
let metricsLogger = MetricsLogging.Logger() Example set of protocols
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
var numbers = [3, 2, 1]
numbers.removeAll(where: { number in
number == numbers.count
}) Thread safety in Swift
var numbers = [3, 2, 1]
Task { numbers.append(0) }
numbers.removeLast() A distributed actor player and a distributed function
distributed actor Player {
var ai: PlayerBotAI?
var gameState: GameState
distributed func makeMove() -> GameMove {
return ai.decideNextMove(given: &gameState)
}
} A distributed actor call
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
if let mailmapURL = mailmapURL {
mailmapLines = try String(contentsOf: mailmapURL).split(separator: "\n")
} Optional unwrapping with long variable names
if let workingDirectoryMailmapURL = workingDirectoryMailmapURL {
mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
} Cryptic abbreviated variable names
if let wdmu = workingDirectoryMailmapURL {
mailmapLines = try String(contentsOf: wdmu).split(separator: "\n")
} Unwrapping optionals in Swift 5.7
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
let entries = mailmapLines.compactMap { line in
try? parseLine(line)
}
func parseLine(_ line: Substring) throws -> MailmapEntry { … } Type inference for complicated closures
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...
// 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
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
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
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?
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
"Becca Royal-Gordon <[email protected]> # Comment"
/ space name space < email > space # or EOL /
/ \h* ( [^<#]+? )?? \h* < ( [^>#]+ ) > \h* (?: #|\Z) / Swift Regex using a literal
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?
/\h*([^<#]+?)??\h*<([^>#]+)>\h*(?:#|\Z)/ Regex builder
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
struct MailmapLine: RegexComponent {
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
struct MailmapLine: RegexComponent {
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
struct DatedMailmapLine: RegexComponent {
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
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
/// 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
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`
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
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
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
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
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
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
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
struct AnySprocket: Sprocket {
private var box: any Sprocket
// …fewer lines of code you hate
// having to maintain…
} Or try type aliases
typealias AnySprocket = any Sprocket `any` types have important limitations
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
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`
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
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
Related sessions
-
26 min -
25 min -
29 min -
27 min -
26 min -
13 min -
25 min -
23 min -
22 min -
15 min -
14 min -
25 min -
24 min