Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-06-14

Code Sample: Using NSRegularExpression from Swift

Regular expressions, love them or hate them but sometimes we just have to use them.

I found myself in the situation that I had to iterate over a Swift-String using a regex expression, and then had to execute a bit of code on the characters that were found.

Luckily Apple has implemented the NSRegularExpression class so we don't have to roll our own.
And that class offers the enumerateMatchesInString operator that will repeatedly call a routine of our own with as input the range of string that satisfies the regex.

In code this looks as follows:

if let regex = NSRegularExpression(pattern: str, options: NSRegularExpressionOptions.allZeros, error: nil) {
    var maxLoopCount: Int = 0
    var range = NSMakeRange(0, count(line))
    regex.enumerateMatchesInString(
        line,
        options: NSMatchingOptions.allZeros,
        range: range,
        usingBlock: {
            [unowned self] (textCheckingResult, matchingFlags, stop) -> Void in
            if textCheckingResult.range.length > 0 {
                let start = advance(self.line.startIndex, textCheckingResult.range.location)
                let end = advance(start, textCheckingResult.range.length - 1)
                for i in start ... end {
                    // ... process result of regex
                }
            }
            maxLoopCount += 1
            if maxLoopCount > 100 { stop.memory = true }
        }
    )
} else {
    // ... there is an error in the regex expression
}

The "str" parameter contains the regex expression, it is a Swift String.
The "line" variable refers to the Swift String that must be parsed by the regex.
In the first line, the regex object is created. I did not use the error info because the result of the creation is simply "nil" when there is an error in the regex expression.
The maxLoopCount is a protection against endless loops, can be omitted at your own risk....
Before calling the enumerateMatchesInString it is necessary to create an NSRange that specifies the range of line that must be parsed. A Swift Range won't do.
The "usingBlock" closure has one special parameter, the "UnsafeMutablePointer<ObjCBool>" which might cause some head-scratching. This is a variable that is created and allocated by the enumerateMatchesInString method, hence it is available to the closure through a pointer. Within the closure it can be used to signal back to the enumerateMatchesInString method that it should stop processing. In the example above it is used as a protection against endless loops. (Necessary or not...)
The closure itself can be called for zero length ranges, hence the protection against it as the first test in the closure. Without this protection the "advance(start, textCheckingResult.range.length - 1)" would create a crash.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment