原做者 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 複製代碼
可是蘋果爸爸表示了拒絕。