swift中的Substring与String

2019-04-30  本文已影响0人  彭磊PL

为文字字符串添加特性或者语法糖在各种编程语言中都很普遍。就拿大家都很熟悉的C语言举例,C字符串本质是一个字符数组,但是每次输入字符串的时候不用输入['h', 'e', 'l', 'l', 'o'],直接用hello就可以了,因为这个操作编译器帮你做了。更高级的语言比如swift处理字符串就不仅仅是当做字符数组了,String是一个完整的类型,并且有各种特性。我们先来看一下String的一个特性:substring。

简单的看一下string

首先粗略的了解一下字符串的实现,以下来自标准库的String.swift文件:

public struct String {
  public var _core: _StringCore
}

里面还有很多的构造函数,不过整个主类型里只有一个存储属性!好东西一定都在StringCore.swift里:

public struct _StringCore {
  public var _baseAddress: UnsafeMutableRawPointer?
  var _countAndFlags: UInt
  public var _owner: AnyObject?
}

在这个类型里还有很多其他东西,不过我们还是只关注存储属性:

Substring

Swift 中要怎么创建一个 substring?最简单的方式就是通过下标从 string 取一段:

let str = "Hello Swift!"
let slice = str[str.startIndex..<str.index(str.startIndex, offsetBy: 5)]
// "Hello"

虽然很简单,但是代码看起来不太优雅。
String 的索引不是直观的整型,所以截取时的位置索引需要利用 startIndex 和 index(_:offsetBy:)获取。如果是从字符串开始位置截取,可以省略掉 startIndex :

let withPartialRange = str[..<str.index(str.startIndex, offsetBy: 5)]
// still "Hello"

或者用 collection 中的这个方法:

let slice = str.prefix(5)
// still "Hello"

要记住字符串也是 collection ,所以你可以用集合下的方法,比如 prefix(),suffix(), dropFirst() 等。

Substring的内部原理

substring 一个神奇的地方是他们重用了父 string 的内存。你可以把 substring 理解为父 string 的其中一段。


image.png

举个例子,如果从一个 8000 个字符的字符串中截取 100 个字符,并不需要重新初始化 100 个字符的内存空间。
这也意味着你可能不小心就把父 string 的生命周期延长了。如果有一大段字符串,然后你只是截取了一小段,只要截取的小段字符串没有释放,大段的字符串也不会被释放。
Substring 内部到底是怎么做到的呢?

public struct Substring {
  internal var _slice: RangeReplaceableBidirectionalSlice<String>
}

内部的 _slice 属性保存着所有关于父字符串的信息:

// Still inside Substring
internal var _wholeString: String {
  return _slice._base
}
public var startIndex: Index { return _slice.startIndex }
public var endIndex: Index { return _slice.endIndex }

计算属性 _wholeString(返回整个父字符串),startIndex 和 endIndex 都是通过内部的 _slice 返回。
也可以看出 slice 是如何引用父字符串的。

Substring转换为String

最后代码里可能有很多 substring,但是函数的参数类型需要的是 string。Substring 转换到 string 的过程也很简单:

let string = String(substring)

因为 substrings 和它的父字符串共享同一个内存空间,猜测创建一个新字符串应该会初始化一片新的存储空间。那么 string 的初始化到底过程是怎样的呢。

extension String {
  public init(_ substring: Substring) {
    // 1
    let x = substring._wholeString
    // 2
    let start = substring.startIndex
    let end = substring.endIndex
    // 3
    let u16 = x._core[start.encodedOffset..<end.encodedOffset]
    // 4A
    if start.samePosition(in: x.unicodeScalars) != nil
    && end.samePosition(in: x.unicodeScalars) != nil {
      self = String(_StringCore(u16))
    }
    // 4B
    else {
      self = String(decoding: u16, as: UTF16.self)
    }
  }
}

1.创建一个对原有父字符串的引用
2.获取substring在父字符串中的开始和结束位置
3.获取UTF-16格式的substring内容。_core是_StringCore的一个实例
4.判断匹配的unicode编码,生成一个新的字符串实例
把 substring 转换成 string 的步骤非常简单,但是你可能要考虑是不是一需要这样做。是不是进行 substring 操作的时候都要求类型是 string?如果对 substring 的操作都需要转成 string,那么轻量级的 substring 也就失去了意义。🤔

StringProtocol

StringProtocol上场!StringProtocol真是面向协议变成的一个优秀代表。
StringProtocol抽象了字符串的常见功能,比如uppercased(), lowercased(),还有comparable,collection等。String和Substring都声明了StringProtocol。也就是说你可以直接使用==对substring和string进行判等,不需要类型转换:

let helloSwift = "Hello Swift"
let swift = helloSwift[helloSwift.index(helloSwift.startIndex, offsetBy: 6)...]

// comparing a substring to a string 
swift == "Swift"  // true

也可以遍历 substring,或者从 substring 截取子字符串。
在标准库里也有一小部分函数使用 StringProtocol 类型作为参数。比如把一个字符串转换为整型就是:init(text: StringProtocol)。
虽然你可能不关心是 string 和 substring,但是使用 StringProtocol 作为参数类型,调用者就不用进行类型转换,对他们会友好很多。

总结

/// Do not declare new conformances to `StringProtocol`. Only the `String` and
/// `Substring` types in the standard library are valid conforming types.
public protocol StringProtocol

但是苹果爸爸表示了拒绝。

上一篇 下一篇

猜你喜欢

热点阅读