Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the correct length: argument to provide to NSRange for NSRegularExpression using a (Swift) String?

Tags:

ios

swift

I'm confused on how to use the NSRegularExpression class in Swift, especially the :length parameter of NSRange.

Some tutorials say that NSRegularExpression should only be applied to NSString instances, while others say it's OK to apply it to (Swift) string instances as long as you provide utf8.count or utf16.count to :length parameter of NSRange:

var str : String = "#tweak #wow #gaming" 
if let regex = try? NSRegularExpression(pattern: "#[a-z0-9]+", options: .caseInsensitive) {
    regex.matches(in: str, options: [], range: NSRange(location: 0, length: str.utf8.count)).map {
        print(str.substring(with: $0.range))
    }
}

The following are quotes from this source:

Due to the way strings are handled differently in Swift and Objective-C, you will need to provide the NSRange instance with a string length from NSString, and not from String.

This is, roughly speaking, because NSString uses fixed-width encoding and String uses variable-width encoding.

Furthermore, is the following documentation really the best Apple can do with respect to documenting the NSRegularExpression class in Swift?

https://developer.apple.com/documentation/foundation/nsregularexpression

I'd at least expect a list of properties and methods of the class, but it only show some examples. Is there any more elaborate documentation?

like image 598
Shuzheng Avatar asked Jan 23 '26 20:01

Shuzheng


1 Answers

We would use the utf16 count. But, it is best to let the framework do this conversion for you. To convert a Range<String.Index> to a NSRange:

let range = NSRange(string.startIndex..., in: string)

And to convert NSRange to Range<String.Index>:

let range = Range(nsRange, in: string)

Thus, putting that together:

let string = "#tweak #wow #gaming" 
if let regex = try? NSRegularExpression(pattern: "#[a-z0-9]+", options: .caseInsensitive) {
    let nsRange = NSRange(string.startIndex..., in: string)
    let strings = regex.matches(in: string, range: nsRange).compactMap {
        Range($0.range, in: string).map { string[$0] }
    }
    print(strings)
}

See WWDC 2017 Efficient Interactions with Frameworks, which talks about (a) our historical use of UTF16 when dealing with ranges; and (b) the fact that we don’t have to do that any more. [This video is no longer available.]


That having been said, nowadays we would retire the NSRegularExpression and use Regex, which eliminates the NSRange entirely:

let string = "#tweak #wow #gaming"
let strings = string.matches(of: /#[a-zA-Z0-9]+/)
    .map { string[$0.range] }

Or

let regex = /#[a-z0-9]+/
    .ignoresCase()

let strings = string.matches(of: regex)
    .map { string[$0.range] }

For more information, see WWDC 2022 video Meet Swift Regex, and the related videos listed on that page.

like image 99
Rob Avatar answered Jan 25 '26 13:01

Rob



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!