@@ -1452,6 +1452,18 @@ extension NSMutableString {
14521452 }
14531453 return 0
14541454 }
1455+
1456+ private static func makeFindResultsRangeIterator( findResults: CFArray , count: Int , backwards: Bool ) -> AnyIterator < NSRange > {
1457+ var index = 0
1458+ return AnyIterator < NSRange > ( ) { ( ) -> NSRange ? in
1459+ defer { index += 1 }
1460+ if index < count {
1461+ let rangePtr = CFArrayGetValueAtIndex ( findResults, backwards ? count - index - 1 : index)
1462+ return NSRange ( rangePtr!. load ( as: CFRange . self) )
1463+ }
1464+ return nil
1465+ }
1466+ }
14551467
14561468 public func replaceOccurrences( of target: String , with replacement: String , options: CompareOptions = [ ] , range searchRange: NSRange ) -> Int {
14571469 let backwards = options. contains ( . backwards)
@@ -1462,19 +1474,35 @@ extension NSMutableString {
14621474 if options. contains ( . regularExpression) {
14631475 return _replaceOccurrencesOfRegularExpressionPattern ( target, withTemplate: replacement, options: options, range: searchRange)
14641476 }
1465-
14661477
1467- if let findResults = CFStringCreateArrayWithFindResults ( kCFAllocatorSystemDefault, _cfObject, target. _cfObject, CFRange ( searchRange) , options. _cfValue ( true ) ) {
1468- let numOccurrences = CFArrayGetCount ( findResults)
1469- for cnt in 0 ..< numOccurrences {
1470- let rangePtr = CFArrayGetValueAtIndex ( findResults, backwards ? cnt : numOccurrences - cnt - 1 )
1471- replaceCharacters ( in: NSRange ( rangePtr!. load ( as: CFRange . self) ) , with: replacement)
1478+ guard let findResults = CFStringCreateArrayWithFindResults ( kCFAllocatorSystemDefault, _cfObject, target. _cfObject, CFRange ( searchRange) , options. _cfValue ( true ) ) else {
1479+ return 0
1480+ }
1481+ let numOccurrences = CFArrayGetCount ( findResults)
1482+
1483+ let rangeIterator = NSMutableString . makeFindResultsRangeIterator ( findResults: findResults, count: numOccurrences, backwards: backwards)
1484+
1485+ guard type ( of: self ) == NSMutableString . self else {
1486+ // If we're dealing with non NSMutableString, mutations must go through `replaceCharacters` (documented behavior)
1487+ for range in rangeIterator {
1488+ replaceCharacters ( in: range, with: replacement)
14721489 }
1490+
14731491 return numOccurrences
1474- } else {
1475- return 0
14761492 }
14771493
1494+ var newStorage = Substring ( )
1495+ var sourceStringCurrentIndex = _storage. startIndex
1496+ for range in rangeIterator {
1497+ let matchStartIndex = String . Index ( utf16Offset: range. location, in: _storage)
1498+ let matchEndIndex = String . Index ( utf16Offset: range. location + range. length, in: _storage)
1499+ newStorage += _storage [ sourceStringCurrentIndex ..< matchStartIndex]
1500+ newStorage += replacement
1501+ sourceStringCurrentIndex = matchEndIndex
1502+ }
1503+ newStorage += _storage [ sourceStringCurrentIndex ..< _storage. endIndex]
1504+ _storage = String ( newStorage)
1505+ return numOccurrences
14781506 }
14791507
14801508 public func applyTransform( _ transform: String , reverse: Bool , range: NSRange , updatedRange resultingRange: NSRangePointer ? ) -> Bool {
0 commit comments