Appearance
Swift Regex
Processing strings to extract, manipulate, or search data is a core skill that most software engineers need to acquire.
Regular expressions, also known as regex, are powerful tools for working with strings.
Swift Regex brings first-class support for regular expressions to the Swift language.
The Swift compiler natively supports regex syntax, which gives us compile time errors, syntax highlighting, and strongly typed captures.
Create a Regex
You can create a Regex
instance using regular expression syntax, either in a regex literal or a string.
Compile Time Regex
swift
// creating a Regex using a regex literal
let keyAndValue = /(.+?): (.+)/
// creating a Regex using a regex literal
let keyAndValue = /(.+?): (.+)/
This regular expression is a first-class type in Swift and can be assigned directly to a variable.
As a Swift type, Xcode will also recognize this regex and provide both compile time checks and syntax highlighting.
Runtime Regex
Swift Regex also supports creating regular expressions at runtime.
swift
// creating a Regex from a pattern in a string
let simpleDigits = try Regex("[0-9]+")
// creating a Regex from a pattern in a string
let simpleDigits = try Regex("[0-9]+")
Here the regular expression is created by constructing the Regex type with a String representing the regular expression. The try keyword is used since a Regex can throw an error if the supplied regular expression is invalid.
Regex Builder
The new Regex Builder introduces a declarative approach to composing regular expressions.
swift
import RegexBuilder
let regex = Regex {
One(.digit)
}
import RegexBuilder
let regex = Regex {
One(.digit)
}
Check for the presence of a pattern
Calling contains(_:) to check for the presence of a pattern
swift
let simpleDigits = try Regex("[0-9]+")
let setting = "color: 161 103 230"
if setting.contains(simpleDigits) {
print("'\(setting)' contains some digits.")
}
// Prints "'color: 161 103 230' contains some digits."
let simpleDigits = try Regex("[0-9]+")
let setting = "color: 161 103 230"
if setting.contains(simpleDigits) {
print("'\(setting)' contains some digits.")
}
// Prints "'color: 161 103 230' contains some digits."
Search for a pattern in a string
When you find a match, the resulting Regex.Match type includes an output property that contains the matched substring along with any captures.
firstMatch(of:)
The firstMatch(of:) method returns the first match for the regex within this collection
swift
let keyAndValue = /(.+?): (.+)/
let setting = "color: 161 103 230"
if let match = setting.firstMatch(of: keyAndValue) {
print("Key: \(match.1)")
print("Value: \(match.2)")
}
// Key: color
// Value: 161 103 230
let keyAndValue = /(.+?): (.+)/
let setting = "color: 161 103 230"
if let match = setting.firstMatch(of: keyAndValue) {
print("Key: \(match.1)")
print("Value: \(match.2)")
}
// Key: color
// Value: 161 103 230
The ?
in the (.+?)
part means lazy mode.
matches(of:)
The matches(of:) method returns a collection containing all non-overlapping matches of the regex.
wholeMatch(of:)
The wholeMatch(of:) method matches a regex in its entirety.
swift
let search1 = /My name is (.+?) and I'm (\d+) years old./
let greeting1 = "My name is Taylor and I'm 26 years old."
if let result = try? search1.wholeMatch(in: greeting1) {
print("Name: \(result.1)")
print("Age: \(result.2)")
}
let search1 = /My name is (.+?) and I'm (\d+) years old./
let greeting1 = "My name is Taylor and I'm 26 years old."
if let result = try? search1.wholeMatch(in: greeting1) {
print("Name: \(result.1)")
print("Age: \(result.2)")
}
That creates a regex looking for two particular values in some text, and if it finds them both prints them. But notice how the result
tuple can reference its matches as .1
and .2
, because Swift knows exactly which matches will occur. (In case you were wondering, .0
will return the whole matched string.)
swift
let search2 = /My name is (?<name>.+?) and I'm (?<age>\d+) years old./
let greeting2 = "My name is Taylor and I'm 26 years old."
if let result = try? search2.wholeMatch(in: greeting2) {
print("Name: \(result.name)")
print("Age: \(result.age)")
}
let search2 = /My name is (?<name>.+?) and I'm (?<age>\d+) years old./
let greeting2 = "My name is Taylor and I'm 26 years old."
if let result = try? search2.wholeMatch(in: greeting2) {
print("Name: \(result.name)")
print("Age: \(result.age)")
}
Call this method if you want the regular expression to succeed only when it matches the entire string you pass as string.
swift
let digits = /
/
if let digitsMatch = try digits.wholeMatch(in: "2022") {
print(digitsMatch.0)
} else {
print("No match.")
}
// Prints "2022"
if let digitsMatch = try digits.wholeMatch(in: "The year is 2022.") {
print(digitsMatch.0)
} else {
print("No match.")
}
// Prints "No match."
let digits = /
/
if let digitsMatch = try digits.wholeMatch(in: "2022") {
print(digitsMatch.0)
} else {
print("No match.")
}
// Prints "2022"
if let digitsMatch = try digits.wholeMatch(in: "The year is 2022.") {
print(digitsMatch.0)
} else {
print("No match.")
}
// Prints "No match."
prefixMatch(of:):
The prefixMatch(of:) returns a match if this string is matched by the given regex at its start.
In fact, we can go even further because regular expressions allow us to name our matches, and these flow through to the resulting tuple of matches:
Parse top command with RegexBuilder
shell
top -l 1 -o mem -n 8 -stats pid,command,pstate,mem | sed 1,12d
top -l 1 -o mem -n 8 -stats pid,command,pstate,mem | sed 1,12d
The command above outputs:
18173* Xcode sleeping 4667M
41019* com.apple.WebKit sleeping 4034M
29685* lldb-rpc-server sleeping 3285M
395* WindowServer sleeping 1322M
18360* XCBBuildService sleeping 1058M
1008* Google Chrome He sleeping 986M
18203* com.apple.dt.SKA sleeping 801M
70744* Google Chrome He sleeping 792M
18173* Xcode sleeping 4667M
41019* com.apple.WebKit sleeping 4034M
29685* lldb-rpc-server sleeping 3285M
395* WindowServer sleeping 1322M
18360* XCBBuildService sleeping 1058M
1008* Google Chrome He sleeping 986M
18203* com.apple.dt.SKA sleeping 801M
70744* Google Chrome He sleeping 792M
Pase output above with RegexBuilder
swift
import RegexBuilder
let top = """
18173* Xcode sleeping 4667M
41019* com.apple.WebKit sleeping 4034M
29685* lldb-rpc-server sleeping 3285M
395* WindowServer sleeping 1322M
18360* XCBBuildService sleeping 1058M
1008* Google Chrome He sleeping 986M
18203* com.apple.dt.SKA sleeping 801M
70744* Google Chrome He sleeping 792M
"""
let separator = /\s{1,}/
let topMatcher = Regex {
OneOrMore(.digit)
ZeroOrMore("*")
separator
Capture(
OneOrMore(.any, .reluctant)
)
separator
Capture(
ChoiceOf {
"running"
"sleeping"
"stuck"
"idle"
"stopped"
"halted"
"zombie"
"unknown"
}
)
separator
Capture {
OneOrMore(.digit)
// /M|K|B/
ChoiceOf {
"M"
"K"
"B"
}
Optionally(/\+|-/)
}
}
// 8
let matches = top.matches(of: topMatcher)
for match in matches {
// 9
let (_, name, status, size) = match.output
print("\(name) \t\t \(status) \t\t \(size)")
}
import RegexBuilder
let top = """
18173* Xcode sleeping 4667M
41019* com.apple.WebKit sleeping 4034M
29685* lldb-rpc-server sleeping 3285M
395* WindowServer sleeping 1322M
18360* XCBBuildService sleeping 1058M
1008* Google Chrome He sleeping 986M
18203* com.apple.dt.SKA sleeping 801M
70744* Google Chrome He sleeping 792M
"""
let separator = /\s{1,}/
let topMatcher = Regex {
OneOrMore(.digit)
ZeroOrMore("*")
separator
Capture(
OneOrMore(.any, .reluctant)
)
separator
Capture(
ChoiceOf {
"running"
"sleeping"
"stuck"
"idle"
"stopped"
"halted"
"zombie"
"unknown"
}
)
separator
Capture {
OneOrMore(.digit)
// /M|K|B/
ChoiceOf {
"M"
"K"
"B"
}
Optionally(/\+|-/)
}
}
// 8
let matches = top.matches(of: topMatcher)
for match in matches {
// 9
let (_, name, status, size) = match.output
print("\(name) \t\t \(status) \t\t \(size)")
}
The code above gives us the following output:
Xcode sleeping 4667M
com.apple.WebKit sleeping 4034M
lldb-rpc-server sleeping 3285M
WindowServer sleeping 1322M
XCBBuildService sleeping 1058M
Google Chrome He sleeping 986M
com.apple.dt.SKA sleeping 801M
Google Chrome He sleeping 792M
Xcode sleeping 4667M
com.apple.WebKit sleeping 4034M
lldb-rpc-server sleeping 3285M
WindowServer sleeping 1322M
XCBBuildService sleeping 1058M
Google Chrome He sleeping 986M
com.apple.dt.SKA sleeping 801M
Google Chrome He sleeping 792M
References
- NSRegularExpression | Apple Developer Documentation
- Regex | Apple Developer Documentation
- RegexBuilder | Apple Developer Documentation
- Regular expressions
- wholeMatch(in:)
- Greedy and lazy quantifiers
- Regular Expressions Tutorial
- Regular Expressions Quick Start
- Sample Regular Expressions
- Swift Regex Deep Dive
- Meet Swift Regex – WWDC 2022
- Swift Regex: Beyond the basics – WWDC 2022
- Swift Regex DSL Builder