【问题标题】:Swift struct extension add initializerSwift 结构扩展添加初始化程序
【发布时间】:2017-07-31 23:38:56
【问题描述】:

我正在尝试将初始化程序添加到 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)')的值转换为预期的参数类型' (下:_,上:_)'

如何编译?

【问题讨论】:

标签: swift compiler-errors range


【解决方案1】:

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

注意:由于NSString 使用UTF-16,请检查this 以及您提到的link,您的初始代码不能正确用于字符由多个 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,您可以查看the Swift source code 做出决定。在 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
  }
}

【讨论】:

  • 我不明白您为什么需要使用string 的UTF-16 编码。我的初始代码似乎可以正常工作,即使对于带有表情符号的字符串也是如此。请提供一个反例。
  • 回答你的问题会很长,所以我决定更新我的答案。我还对示例进行了一些编辑,以使差异更加明显。
  • 嗯,当然,我看到了区别,但我仍然不明白为什么你的例子中的结果是正确的。我想我会相信苹果的话。谢谢! :-)
【解决方案2】:

您需要将范围初始值设定项限制在 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
    }
}

【讨论】:

    【解决方案3】:

    该方法的签名需要“绑定”类型(至少在 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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-11
      • 1970-01-01
      相关资源
      最近更新 更多