Created
October 27, 2018 22:18
-
-
Save zaimramlan/2597b024162a88acb8db90fdb99b0fbe to your computer and use it in GitHub Desktop.
Swift's String(format:arguments:) method (peeking under the hood)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension String { | |
/// Replaces format specifiers (with index) with given arguments. | |
/// (Only works if given arguments are Strings and to replace Strings) | |
/// | |
/// - Parameter arguments: List of String replacements. | |
/// - Returns: This String replaced with given Strings (unless arguments are not a String array) | |
func replaceFormatSpecifiersWithIndex(with arguments: CVarArg...) -> String { | |
guard let args = arguments as? [String] else { return self } | |
// ensure patterns are valid | |
let formatSpecifierPattern = "%[\\d]+\\$@" | |
let digitPattern = "[\\d]+" | |
guard | |
let formatSpecifierRegEx = try? NSRegularExpression(pattern: formatSpecifierPattern, options: .useUnixLineSeparators), | |
let digitRegEx = try? NSRegularExpression(pattern: digitPattern, options: .useUnixLineSeparators) | |
else { return self } | |
// extract range of format specifiers (if any) | |
var range = NSRange(location: 0, length: self.count) | |
var matches = formatSpecifierRegEx.matches(in: self, options: .withoutAnchoringBounds, range: range) | |
let mutableSelf = NSMutableString(string: self) | |
while matches.count > 0 { | |
// extract index from format specifier | |
guard let matchingFormatSpecifierRange = matches.first?.range else { break } | |
let formatSpecifier = mutableSelf.substring(with: matchingFormatSpecifierRange) | |
let digitRange = NSRange(location: 0, length: formatSpecifier.count) | |
let digitMatches = digitRegEx.matches(in: formatSpecifier, options: .withoutAnchoringBounds, range: digitRange) | |
guard let matchingDigitRange = digitMatches.first?.range else { break } | |
let mutableFormatSpecifier = NSMutableString(string: formatSpecifier) | |
let index = NSDecimalNumber(string: mutableFormatSpecifier.substring(with: matchingDigitRange)).intValue - 1 | |
// try to replace string | |
let replacementString = index < args.count ? args[index] : "(null)" | |
mutableSelf.replaceCharacters(in: matchingFormatSpecifierRange, with: replacementString) | |
// overwrite range & matches to reflect newly replaced string | |
range = NSRange(location: 0, length: mutableSelf.description.count) | |
matches = formatSpecifierRegEx.matches(in: mutableSelf.description, options: .withoutAnchoringBounds, range: range) | |
} | |
return mutableSelf.description | |
} | |
/// Replaces format specifiers (without index) with given arguments. | |
/// (Only works if given arguments are Strings and to replace Strings) | |
/// | |
/// - Parameter arguments: List of String replacements. | |
/// - Returns: This String replaced with given Strings (unless arguments are not a String array) | |
func replaceFormatSpecifierWithoutIndex(with arguments: CVarArg...) -> String { | |
guard let args = arguments as? [String] else { return self } | |
// ensure pattern is valid | |
let formatSpecifierPattern = "%@" | |
guard let regEx = try? NSRegularExpression(pattern: formatSpecifierPattern, options: .useUnixLineSeparators) else { return self } | |
// extract range of format specifiers (if any) | |
var range = NSRange(location: 0, length: self.count) | |
var matches = regEx.matches(in: self, options: .withoutAnchoringBounds, range: range) | |
let mutableSelf = NSMutableString(string: self) | |
var index = 0 | |
while matches.count > 0 { | |
// try to replace string | |
guard let matchingFormatSpecifierRange = matches.first?.range else { break } | |
let replacementString = index < args.count ? args[index] : "(null)" | |
mutableSelf.replaceCharacters(in: matchingFormatSpecifierRange, with: replacementString) | |
// overwrite range & matches to reflect newly replaced string | |
range = NSRange(location: 0, length: mutableSelf.description.count) | |
matches = regEx.matches(in: mutableSelf.description, options: .withoutAnchoringBounds, range: range) | |
index += 1 | |
} | |
return mutableSelf.description | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment