快速结构扩展添加初始值设定项



我正在尝试向Range添加一个初始值设定项。

import Foundation
extension Range {
init(_ range: NSRange, in string: String) {
let lower = string.index(string.startIndex, offsetBy: range.location)
let upper = string.index(string.startIndex, offsetBy: NSMaxRange(range))
self.init(uncheckedBounds: (lower: lower, upper: upper))
}
}

但是,最后一行有一个 Swift 编译器错误。

无法将类型"(下:字符串.索引,上:字符串("的值(又名"(下:字符串.字符视图.索引,上:

字符串.字符视图.索引("(转换为预期的参数类型"(下:_,上:_(">

如何编译它?

问题是即使String.Index确实符合Comparable协议,您仍然需要指定要使用的范围类型public struct Range<Bound> where Bound : Comparable {}

注意:由于NSString使用 UTF-16,请检查这一点,并且在您提到的链接中,对于由多个 UTF-16 代码点组成的字符,您的初始代码无法正常工作。以下是 Swift 3 的更新工作版本。

extension Range where Bound == String.Index {
init(_ range: NSRange, in string: String) {
let lower16 = string.utf16.index(string.utf16.startIndex, offsetBy: range.location)
let upper16 = string.utf16.index(string.utf16.startIndex, offsetBy: NSMaxRange(range))
if let lower = lower16.samePosition(in: string),
let upper = upper16.samePosition(in: string) {
self.init(lower..<upper)
} else {
fatalError("init(range:in:) could not be implemented")
}
}
}
let string = "❄️Let it snow! ☃️"
let range1 = NSRange(location: 0, length: 1)
let r1 = Range<String.Index>(range1, in: string) // ❄️
let range2 = NSRange(location: 1, length: 2)
let r2 = Range<String.Index>(range2, in: string) // fatal error: init(range:in:) could not be implemented

要回答OP的评论:问题是NSString对象编码一个符合Unicode的文本字符串,表示为一系列UTF-16代码单元。构成字符串内容的 Unicode 标量值最长可达 21 位。较长的标量值可能需要两个 UInt16 值进行存储。

因此,一些字母在 ❄️ NSString 中占用两个 UInt16 值,但在字符串中只占用一个。当您将 NSRange 参数传递给初始值设定项时,您可能希望它在 NSString 中正常工作。

在我的示例中,将string转换为 utf16 后r1r2的结果是"❄️"和致命错误。同时,原始解决方案的结果分别为"❄️L"和"Le"。希望您能看到其中的区别。

如果您坚持使用解决方案而不转换为 utf16,您可以查看 Swift 源代码来做出决定。在 Swift 4 中,你将拥有初始值设定项作为内置库。代码如下。

extension Range where Bound == String.Index {
public init?(_ range: NSRange, in string: String) {
let u = string.utf16
guard range.location != NSNotFound,
let start = u.index(u.startIndex, offsetBy: range.location, limitedBy: u.endIndex),
let end = u.index(u.startIndex, offsetBy: range.location + range.length, limitedBy: u.endIndex),
let lowerBound = String.Index(start, within: string),
let upperBound = String.Index(end, within: string)
else { return nil }
self = lowerBound..<upperBound
}
}

您需要将范围初始值设定项限制在 Bound 等于 String.Index 的位置,获取 NSRange utf16 索引,并在字符串中找到字符串索引的相同位置,如下所示:

extension Range where Bound == String.Index {
init?(_ range: NSRange, in string: String) {
guard
let start = string.utf16.index(string.utf16.startIndex, offsetBy: range.location, limitedBy: string.utf16.endIndex),
let end = string.utf16.index(string.utf16.startIndex, offsetBy: range.location + range.length, limitedBy: string.utf16.endIndex),
let startIndex = start.samePosition(in: string),
let endIndex = end.samePosition(in: string)
else {
return nil
}
self = startIndex..<endIndex
}
}

该方法的签名需要"绑定"类型(至少在 swift 4 中(

由于 Bound 只是"可比"的关联类型,而 String.Index 符合它,因此您应该能够强制转换它。

extension Range {
init(_ range: NSRange, in string: String) {
let lower : Bound = string.index(string.startIndex, offsetBy: range.location) as! Bound
let upper : Bound = string.index(string.startIndex, offsetBy: NSMaxRange(range)) as! Bound
self.init(uncheckedBounds: (lower: lower, upper: upper))
}
}

https://developer.apple.com/documentation/swift/rangeexpression/2894257-bound

最新更新