Swift Regular Expressions
Regular expressions (regex) are powerful tools for working with text patterns in Swift. They provide a concise and flexible way to search, match, and manipulate strings based on pattern rules. Swift has built-in support for regular expressions that makes processing text more efficient and expressive.
What are Regular Expressions?
Regular expressions are special string patterns that describe search patterns. Think of them as a mini-language for specifying text patterns. They can be used to:
- Validate input (like email addresses or phone numbers)
- Search for specific patterns in text
- Replace or extract parts of strings
- Parse and transform text
Basic Regular Expression Syntax in Swift
Swift offers multiple ways to work with regular expressions. Let's start with the traditional approach using the NSRegularExpression
class.
Using NSRegularExpression
import Foundation
let text = "Contact me at [email protected] or visit [email protected]"
let pattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}"
do {
let regex = try NSRegularExpression(pattern: pattern)
let nsRange = NSRange(text.startIndex..<text.endIndex, in: text)
let matches = regex.matches(in: text, range: nsRange)
for match in matches {
let matchRange = Range(match.range, in: text)!
let email = String(text[matchRange])
print("Found email: \(email)")
}
} catch {
print("Invalid regex: \(error.localizedDescription)")
}
Output:
Found email: [email protected]
Found email: [email protected]
Swift 5.7+ Regex Literals
Swift 5.7 introduced a more modern way to work with regular expressions using regex literals:
import Foundation
let text = "Swift 5.7 was released in 2022"
// Using a regex literal
let yearPattern = /\d{4}/
if let match = text.firstMatch(of: yearPattern) {
print("Found year: \(match.0)")
}
Output:
Found year: 2022
Common Regex Patterns
Let's explore some common regex patterns that you'll frequently use:
1. Character Classes
Character classes match specific sets of characters:
let text = "Swift 5.7"
// Match digits
let digitPattern = /\d+/
if let match = text.firstMatch(of: digitPattern) {
print("Digits: \(match.0)") // Prints: Digits: 5
}
// Match letters
let letterPattern = /[a-zA-Z]+/
if let match = text.firstMatch(of: letterPattern) {
print("Letters: \(match.0)") // Prints: Letters: Swift
}
2. Quantifiers
Quantifiers specify how many times something should match:
let text = "Hello"
// + means one or more
let oneOrMorePattern = /l+/
if let match = text.firstMatch(of: oneOrMorePattern) {
print("One or more 'l': \(match.0)") // Prints: One or more 'l': ll
}
// * means zero or more
let zeroOrMorePattern = /e*/
if let match = text.firstMatch(of: zeroOrMorePattern) {
print("Zero or more 'e': \(match.0)") // Prints: Zero or more 'e': e
}
// ? means zero or one
let optionalPattern = /lo?/
if let match = text.firstMatch(of: optionalPattern) {
print("Optional 'o': \(match.0)") // Prints: Optional 'o': lo
}
3. Anchors
Anchors match positions rather than characters:
let text = "Swift programming"
// ^ matches start of string
let startPattern = /^Swift/
if text.firstMatch(of: startPattern) != nil {
print("Text starts with 'Swift'")
}
// $ matches end of string
let endPattern = /ing$/
if text.firstMatch(of: endPattern) != nil {
print("Text ends with 'ing'")
}
Using Capture Groups
Capture groups allow you to extract specific parts of a matched pattern:
import Foundation
let text = "Date: 2023-07-15"
let datePattern = /Date: (\d{4})-(\d{2})-(\d{2})/
if let match = text.firstMatch(of: datePattern) {
let (fullMatch, year, month, day) = match.output
print("Year: \(year), Month: \(month), Day: \(day)")
}
Output:
Year: 2023, Month: 07, Day: 15
Regex Builder API
Swift 5.7 also introduced the regex builder API, which allows you to build complex regular expressions programmatically:
import RegexBuilder
let text = "Contact: John (ID: 12345)"
let pattern = Regex {
"Contact: "
Capture {
OneOrMore(.word)
}
" (ID: "
Capture {
OneOrMore(.digit)
}
")"
}
if let match = text.firstMatch(of: pattern) {
let (_, name, id) = match.output
print("Name: \(name), ID: \(id)")
}
Output:
Name: John, ID: 12345
Real-World Applications
Let's look at some practical examples of using regular expressions in Swift.
Example 1: Email Validation
import Foundation
func isValidEmail(_ email: String) -> Bool {
let pattern = /^[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
return email.wholeMatch(of: pattern) != nil
}
// Test the function
let emails = ["[email protected]", "invalid@email", "[email protected]"]
for email in emails {
print("\(email) is \(isValidEmail(email) ? "valid" : "invalid")")
}
Output:
[email protected] is valid
invalid@email is invalid
[email protected] is valid
Example 2: Extracting URLs from Text
import Foundation
func extractURLs(from text: String) -> [String] {
let pattern = /(https?:\/\/[^\s]+)/
var urls: [String] = []
var searchRange = text.startIndex..<text.endIndex
while let match = text[searchRange].firstMatch(of: pattern) {
urls.append(String(match.1))
searchRange = match.range.upperBound..<text.endIndex
}
return urls
}
let blogPost = "Check out my website at https://example.com and my blog at http://blog.example.com/posts"
let urls = extractURLs(from: blogPost)
print("Found URLs:")
urls.forEach { print("- \($0)") }
Output:
Found URLs:
- https://example.com
- http://blog.example.com/posts
Example 3: Parsing Structured Data
import Foundation
import RegexBuilder
struct Person {
let firstName: String
let lastName: String
let age: Int
}
func parsePerson(from text: String) -> Person? {
let pattern = Regex {
"Name: "
Capture {
OneOrMore(.word)
}
" "
Capture {
OneOrMore(.word)
}
", Age: "
Capture {
OneOrMore(.digit)
}
}
if let match = text.firstMatch(of: pattern) {
let (_, firstName, lastName, ageString) = match.output
if let age = Int(ageString) {
return Person(firstName: String(firstName), lastName: String(lastName), age: age)
}
}
return nil
}
let text = "Name: John Smith, Age: 32"
if let person = parsePerson(from: text) {
print("Parsed Person: \(person.firstName) \(person.lastName), \(person.age) years old")
}
Output:
Parsed Person: John Smith, 32 years old
Performance Considerations
Regular expressions are powerful but can be computationally expensive:
- Compile once, use many times: If you use the same regex multiple times, compile it once and reuse it.
- Be specific: More specific patterns are often more efficient.
- Avoid backtracking: Complex patterns with many optional parts can cause excessive backtracking.
- Consider alternatives: For simple string operations, Swift's built-in string methods might be faster.
// Inefficient - creates regex each time
func countMatches(in texts: [String]) -> Int {
var count = 0
for text in texts {
if try! NSRegularExpression(pattern: "\\d+").firstMatch(in: text, range: NSRange(text.startIndex..<text.endIndex, in: text)) != nil {
count += 1
}
}
return count
}
// Efficient - creates regex once
func countMatchesEfficient(in texts: [String]) -> Int {
let regex = try! NSRegularExpression(pattern: "\\d+")
var count = 0
for text in texts {
if regex.firstMatch(in: text, range: NSRange(text.startIndex..<text.endIndex, in: text)) != nil {
count += 1
}
}
return count
}
Summary
Regular expressions in Swift provide a powerful way to work with text patterns. We've covered:
- Basic regex syntax and patterns
- Using
NSRegularExpression
for traditional regex operations - Modern Swift regex literals and builders
- Capture groups for extracting matched components
- Real-world applications like validation and data extraction
- Performance considerations
With these skills, you can efficiently handle complex text processing tasks in your Swift applications. Whether you're validating user input, parsing data, or extracting information from text, regular expressions are an essential tool in your Swift programming toolkit.
Additional Resources
- Apple's Documentation on Regular Expressions
- Swift RegexBuilder Documentation
- Regular Expression Testing Tools
Exercises
- Create a function that validates phone numbers in the format (XXX) XXX-XXXX.
- Write a regex to extract hashtags (words prefixed with #) from a tweet.
- Parse a log file with the format "[YYYY-MM-DD HH:MM:SS] [LEVEL] Message" to extract the date, log level, and message.
- Create a function that replaces all URLs in a text with clickable HTML links.
- Build a regex to validate passwords that require at least 8 characters, one uppercase letter, one lowercase letter, and one number.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)