When doing text replacing, like replacing all "dog" in "A dog is a dog in dogs." to "monkey". A common algorithm is:
- get the ranges of "dog" in "A dog is a dog in dogs."
- reversed the ranges and for each of the range, call
String.replaceSubrange(range, with: "monkey")
.
Above algorithm works fine with String. However, if we use NSString, the code will not work.
That is because NSString is lazily bridged to String and it uses UTF-16, when getting the ranges, the ranges are in UTF-16 and when replacing the first range, the NSString bridged String with UTF-16 is converted String with UTF-8. All ranges in UTF-16 are invalidated potentially.
So above algorithm won't work in NSString if people use UTF-8 characters in Asia.
The issue harms to all Apple current SDKs as UIKit and Cocoa are used NSString internally in TextFields and TextViews.
I don't know if this is an already known tradeoff or should be consider as a bug.
Below is a sample playground code.
import Foundation
var s:String = "A dog is a dog in dogs."
var ns:NSString = "A dog is a dog in dogs."
var s1:String = "A dog is a dog in dogs. 字 A dog is a dog in dogs."
var ns1:NSString = "A dog is a dog in dogs. 字 A dog is a dog in dogs."
func ranges(_ content:String, of source:String) -> [Range<String.Index>] {
var ranges = [Range<String.Index>]()
var startIndex = content.startIndex
let endIndex = content.endIndex
while let aRange = content.range(of: source, range: startIndex..<endIndex) {
ranges.append(aRange)
startIndex = aRange.upperBound
}
return ranges
}
func replace(_ content: String, in ranges:[Range<String.Index>], to target:String) -> String {
var source = content
ranges.reversed().forEach {
source.replaceSubrange($0, with: target)
}
return source
}
let sRanges = ranges(s, of: "dog")
s = replace(s, in: sRanges, to: "monkey")
let nsRanges = ranges(ns as String, of: "dog")
ns = replace(ns as String, in: nsRanges, to: "monkey") as NSString
let s1Ranges = ranges(s1, of: "dog")
s1 = replace(s1, in: s1Ranges, to: "monkey")
let ns1Ranges = ranges(ns1 as String, of: "dog")
ns1 = replace(ns1 as String, in: ns1Ranges, to: "monkey") as NSString