Swift ��构体扩展添加初始化器。

3

我正在尝试向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编译器错误。

无法将类型为“(lower: String.Index, upper: String.Index)”(又名“(lower: String.CharacterView.Index, upper: String.CharacterView.Index)”)的值转换为预期的参数类型“(lower: _, upper: _)”

我该如何使其编译通过?


3个回答

1
问题在于,尽管String.Index符合Comparable协议,但您仍需要指定要使用的Range类型public struct Range<Bound> where Bound : Comparable {} 注意:由于NSString使用UTF-16,请检查this以及您所引用的链接,您的初始代码对于由多个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值,但在String中只占用一个。当您将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
  }
}

我不明白为什么需要使用UTF-16编码的string。我的初始代码似乎可以正确地处理甚至带有表情符号的字符串。请提供一个反例。 - ma11hew28
这是一个非常长的评论来回答你的问题,所以我决定更新我的答案。我还稍微编辑了一下我的例子,以使差异更加明显。 - Lawliet
嗯,我确实看到了区别,但我仍然不明白为什么你的例子中结果是正确的。我想我会相信苹果的说法。谢谢! :-) - ma11hew28

1

您需要将范围初始化器限制在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
    }
}

0

该方法的签名需要一个“Bound”类型(至少在Swift 4中)

由于Bound只是“Comparable”的关联类型,而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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接