原做者 Greg Heo (@gregheo) | Twitter ,原文连接:Swift Substringsgit
为文本字符串添加特性或者语法糖在各类编程语言中都很广泛。就拿你们都很熟悉的 C 语言举例,C 字符串本质是一个字符数组(characters array),可是每次输入字符串的时候不用输入 ['h','e','l','l','o']
,直接打 hello
就能够了,由于这个操做编译器帮你作了。 更高级的语言好比 Swift 处理字符串就不只仅是当作字符数组了,String 是一个完整的类型,而且有各类特性。咱们先来看一下 String 的一个特性:substrings。程序员
首先粗略的了解一下字符串的实现。下面的代码来自标准库中 String.swift:github
public struct String {
public var _core: _StringCore
}
复制代码
固然也有一些其余初始化设置,不过在声明里只有这一个存储属性!秘密必定都在 StringCore.swift 里:编程
public struct _StringCore {
public var _baseAddress: UnsafeMutableRawPointer?
var _countAndFlags: UInt
public var _owner: AnyObject?
}
复制代码
在这个类型里还有不少其余东西,不过咱们仍是只关注存储属性:swift
Swift 中要怎么建立一个 substring?最简单的方式就是经过下标从 string 取一段:数组
let str = "Hello Swift!"
let slice = str[str.startIndex..<str.index(str.startIndex, offsetBy: 5)]
// "Hello"
复制代码
虽然很简单,可是代码看起来不太优雅😄。 String 的索引不是直观的整型,因此截取时的位置索引须要利用 startIndex 和 index(_:offsetBy:)
获取。若是是从字符串开始位置截取,能够省略掉 startIndex :app
let withPartialRange = str[..<str.index(str.startIndex, offsetBy: 5)]
// still "Hello"
复制代码
或者用 collection 中的这个方法:编程语言
let slice = str.prefix(5)
// still "Hello"
复制代码
要记住字符串也是 collection ,因此你能够用集合下的方法,好比 prefix(),suffix(), dropFirst() 等。ide
substring 一个神奇的地方是他们重用了父 string 的内存。你能够把 substring 理解为父 string 的其中一段。 函数
举个例子,若是从一个 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 的过程也很简单:
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)
}
}
}
复制代码
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 做为参数类型,调用者就不用进行类型转换,对他们会友好不少。
是否是以为本身也能够自定义字符串类型,实现 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 复制代码
可是苹果爸爸表示了拒绝。