Swift结构扩展添加初始化程序

时间:2017-07-31 23:38:57

标签: swift compiler-errors range

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

  

无法转换类型&#39;(lower:String.Index,upper:String.Index)的值&#39; (又名&#39;(lower:String.CharacterView.Index,upper:String.CharacterView.Index)&#39;)预期参数类型&#39;(lower:_,upper:_)&#39; < / p>

如何编译?

3 个答案:

答案 0 :(得分: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中正常工作。

在我的示例中,将r1转换为utf16后r2string的结果为'❄️'且为致命错误。同时,原始解决方案的结果分别为“❄️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
  }
}

答案 1 :(得分: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
    }
}

答案 2 :(得分:0)

该方法的签名需要&#34; Bound&#34;类型(至少在swift 4中)

由于Bound只是一种相关类型的&#34;可比较&#34;和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