Swift 3.0 標準庫源碼閱讀筆記——String

EsperanzaK5 8年前發布 | 9K 次閱讀 Swift Apple Swift開發

都說代碼是最好的文檔,標準庫的代碼+注釋真的是比官方文檔還有用!

先拿最常用的 String 開刀!

閱讀過程中可以配合 playground 文件: String.playground

用法簡介

Swift 中的 String 是一個 Unicode 的字符串值(struct)

Swift 中的 String 可以和 Objective-C 中的 NSString 相互橋接,很多時候將 String 轉換成 NSString 來做一些針對字符串的處理會更加方便。

Swift 中的 String 還可以很完美地和 C 層進行交互,實現了一些 C 層次的 API,并且結果完全一致。

首先先講講 String 的一些基本用法:

創建

  • string literals 字符串構建,最簡單的構建方式
let greeting = "Welcome!"
  • string interpolation 插值構建
let name = "Rosa"
let personalizedGreeting = "Welcome, \(name)!"

修改

String 為值類型(struct),修改一個 string 的拷貝,原來的不會被影響。

var otherGreeting = greeting
otherGreeting += " Have a nice time!"
print(otherGreeting)
//Prints "Welcome! Have a nice time!"
print(greeting)
// Prints "Welcome!"  原來的不受影響

比較

String 比較的不是字面值,而是標準化為 Unicode 的值

let cafe1 = "Cafe\u{301}"
let cafe2 = "Café"
print(cafe1 == cafe2)
// Prints "true"  最后的值為 true

unicode \u{301} 做的就是把前一個字符做一個升調,可以看到,這兩個字符串是相等的。

String 類型是不受 Locale 影響的。

視圖

String 本身是不可迭代的,需要通過 String 的視圖完成集合相關操作,比如下標。

  • Character View 字符串視圖
let cafe = "Cafe\u{301} du :earth_africa:"
print(cafe.characters.count)
// Prints "9" 注意, string.characters 返回的是一個 CharacterView 不是一個 Array
print(Array(cafe.characters))
// Prints "["C", "a", "f", "é", " ", "d", "u", " ", ":earth_africa:"]"
  • Unicode ScalarView (UTF-32 View)
print (cafe.unicodeScalars.count)  
// Print "10" 
print (Array(cafe.unicodeScalars))
// Prints "["C", "a", "f", "e", "\u{0301}", " ", "d", "u", " ", "\u{0001F30D}"]"

print(cafe.unicodeScalars.map { $0.value })
//Prints "[67, 97, 102, 101, 769, 32, 100, 117, 32, 127757]"

差別很明顯,Character View 是用戶可識別的,針對用戶的, Unicode ScalarView 直接顯示 Unicode·標量, é 由兩個 Unicode 組成,所以前者為9 后者為 10, :earth_africa: 在 Unicode ScarlarView 中直接顯示為 32位 的 Unicode 值

  • UTF-16 View

相比 Unicode ScalarView, UTF-16 顯示的是 16位 的 Unicode

print(cafe.utf16.count)
// Prints "11"
print(Array(cafe.utf16))
// Prints "[67, 97, 102, 101, 769, 32, 100, 117, 32, 55356, 57101]"

可以看到,最后一個字符 “:earth_africa:” 被分割成了兩個 UTF-16 字符

print(Array(":earth_africa:".utf16))
//[55356, 57101]
print(Array(":earth_africa:".unicodeScalars))
//["\u{0001F30D}"]

值得一提的是,之前提過 String 和 NSString 之間可以直接進行橋接,那么將剛才那個字符轉換成 NSString 后再調用其 length 會獲得多大的長度值呢?

let nscafe = cafe as NSString
print (nscafe.length)
// Prints 11
print(nscafe.character(at: 10))
// Print  57101

所以 NSString 用的是 UTF-16 的編碼方式

  • UTF-8 View
print(cafe.utf8.count)
//Prints "14"
print(Array(cafe.utf8))
//240, 159, 140, 141]"

let cLength = strlen(cafe)
print(cLength)
//Prints "14"

strlen 是 C 層次的 API, 所以可以看到 C 中 String 是采取 UTF-8 編碼的

視圖的選擇

為什么 Swift 中 String 要使用視圖? 我們選擇什么樣的視圖取決于使用的用途。 不同的視圖導致 String 返回不同的長度。

一個是用途考慮:用于用戶顯示,針對用戶而言,肯定是 characterView

另一個是考慮兼容: 如果需要和 C 交互 或者轉換成了 NSString ,則需要具體情況具體考慮。

IsEmpty

一個小細節,如果想要判斷字符串是否為空, 不要調用 views.count ,調用 isEmpty ,前者會有一個 O(n) 的時間消耗,會遍歷一遍。

String 元素訪問

我們沒有辦法直接通過 cafe[i] 來獲取到 cafe 的第 i 個元素( i 為一個 Int )

必須使用 String.Index 下標

let index = cafe.characters.index(cafe.startIndex, offsetBy: 8)
print(cafe[index])
//Print ":earth_africa:"

可以看到,直接對 String 做下標訪問,使用的是 CharacterView

再看一個官方例子:

let name = "Marie Curie"
let firstSpace = name.characters.index(of: " ")!
let firstName = String(name.characters.prefix(upTo: firstSpace))
print(firstName)
//Prints "Marie"

通過 name.characters.index(of: " ") 獲得第一個空白字符的 Index(類型為 String.Index),String(characterView) 可以將 CharacterView 轉換成 String。

Index 是可以相互轉換的,同一個字符,在 CharacterView 和 UTF-8 View中很可能位置不同,我們使用下面的方式來進行轉換:

let firstSpaceUTF8 = firstSpace.samePosition(in: name.utf8)
print(Array(name.utf8.prefix(upTo: firstSpaceUTF8)))
//Prints "[77, 97, 114, 105, 101]"

其他 API 可以閱讀官方文檔~ 下面進入源碼階段!

源碼閱讀

<< struct >> String

String的結構體本身非常簡單。

public struct String {
  /// Creates an empty string.
  public init() {
    _core = _StringCore()
  }

  public // @testable
  init(_ _core: _StringCore) {
    self._core = _core
  }

  public // @testable
  var _core: _StringCore
}

可以看到著手點就在 _StringCore 這個對象, 在 String 文件中并沒有找到 _StringCore 的定義,剩下的就是 String 的一大堆擴展,先不針對這些擴展做文章,我們來找 _StringCore

<< struct >> _StringCore

我們可以直接找到 StringCore 的文件, 在里面有 _StringCore 對象的定義。 _StringCore 是一個比較底層的結構體,真正定義了 String 的數據結構。它能夠存儲 ASCII 和 UTF-16, 同時可以包裝 _StringBuffer 或者 NSString 實例

_StringCore 主要有三個實例:

_baseAddress , _countAndFlags , _owner

var s: String? = "Foo"
print(s?.characters)
// Optional(Swift.String.CharacterView(_core: Swift._StringCore(_baseAddress: Optional(0x0000000111b220cc), _countAndFlags: 3, _owner: nil)))

var t: String? = "Foo"
print(t?.characters)

// Optional(Swift.String.CharacterView(_core: Swift._StringCore(_baseAddress: Optional(0x0000000111b220cc), _countAndFlags: 3, _owner: nil)))

t = t! + " Hello"
print(t?.characters)
//Optional(Swift.String.CharacterView(_core: Swift._StringCore(_baseAddress: Optional(0x00006180000595b0), _countAndFlags: 9, _owner: Optional(Swift._HeapBufferStorage<Swift._StringBufferIVars, Swift.UInt16>))))
print(s?.characters)
//Optional(Swift.String.CharacterView(_core: Swift._StringCore(_baseAddress: Optional(0x00000001103ab0cc), _countAndFlags: 3, _owner: nil)))

所以至少可以看到 _baseAddress 指的是 字符串對應的地址指針, _countAndFlags 代表長度 , owner 暫時不知道干嘛的 =.= 待議。但是

這里兩個String對象的 baseAddress 指向了同一個指針,所以有必要解釋下 String 的內存管理機制。

在 Swift 中, String 使用的是 copy-on-write 的策略將數據存儲在 buffer 中,并且 buffer 是可以共享的, 以上述代碼為例, 一開始 “Foo” 這個字符串在緩存區中是不存在的,會新建 “Foo” 這塊緩存, 定義 t 的時候在緩存區找到了 “Foo” 便直接將 _baseAddress 指向 “Foo”, 當修改 String 值的時候, 會重新指向一個新的地址。

在 _StringCore 這個文件中,同樣定義了許多方法,同時 _StringCore 也實現了不少協議,這些我們先跳過,因為 _StringCore 這個對象在實際開發中基本不會使用到, 我們可以在之后涉及到 String 的一些操作的時候回過頭看這些方法。

<< struct >> String.CharacterView

接著我們來看一下 String 的不同視圖, 首先是最常用到的 CharacterView。

String 本身不是一個 Sequence 或者 Collection,所以如果要對 String 中的字符做操作,需要借助其視圖,比如 characters

extension String {
  public struct CharacterView {
    internal var _core: _StringCore
    internal var _coreOffset: Int
    public init(_ text: String) {
      self._core = text._core
      self._coreOffset = 0
    }
    public // @testable
    init(_ _core: _StringCore, coreOffset: Int = 0) {
      self._core = _core
      self._coreOffset = coreOffset
    }
  }
  public var characters: CharacterView {
    get {
      return CharacterView(self)
    }
    set {
      self = String(newValue)
    }
  }
  public init(_ characters: CharacterView) {
    self.init(characters._core)
  }
}

CharacterView 是一個 String 的內部結構體,有兩個私有屬性: _core:_StringCore , _coreOffset: Int , String 定義了一個獲取其 CharacterView 的方法,實際上就是調用了 CharacterView(self) 可以看到, 每次調用 characters 都會重新生成一個 CharacterView, 而 CharacterView 的構造方法實際上是復制了 String 的 _core, 注意,因為 _StringCore 是結構體,所以這里是拷貝。 而 characters 的 set 方法實際上替換了 String 本身。

下面介紹一個在這個結構體定義的時候唯一涉及到的一個功能性質的方法

public mutating func withMutableCharacters<R>(
_ body: (inout CharacterView) -> R
) -> R {
// Naively mutating self.characters forces multiple references to
// exist at the point of mutation. Instead, temporarily move the
// core of this string into a CharacterView.
    var tmp = CharacterView("")
    swap(&_core, &tmp._core)
    let r = body(&tmp)
    swap(&_core, &tmp._core)
    return r
}

使用一個臨時的 CharacterView 作為 tmp, 交換 tmp 和 String 的 _core, 然后對 tmp 做一些操作, 做完之后再交換回來, 那么 String 的 _core 已經是被處理過的了。 函數返回的是這個閉包的返回值。

var str = "All this happened, more or less."
let afterSpace = str.withMutableCharacters { chars -> String.CharacterView in
    if let i = chars.index(of: " ") {
        let result = chars.suffix(from: chars.index(after: i))
        chars.removeSubrange(i..<chars.endIndex)
        return result
  }
  return String.CharacterView()
  }
print(str)
// Prints "All"
print(String(afterSpace))
// Prints "this happened, more or less."

之前也提過, String 本身不是一個集合不可被遍歷, 而 CharacterView 實現了 BidirectionalCollection 協議,是一個集合,可遍歷。

在實現中, 定義了 String.CharacterView.Index 這個對象,即 CharacterView 的下標,這個下標首先是 Comparable 的,調用 CharacterView 的下標方法 [] 中必須是一個 String.CharacterView.Index 對象而不是一個 Int 值。

String.CharacterView.Index 是基于 String.UnicodeScalarView.Index 的,它有一個類型為 String.UnicodeScalarView.Index 的 base 實例, 附帶一個 _countUTF16 的偏移量, 我們可以以之前的”:earth_africa:”,它是兩個 UTF-32 字符,但是是一個 Character,所以一個 base 和一個 count 就可以解決 UnicodeScalarView.Index 到 CharacterView.Index 的轉換。

我們可以猜測, UnicodeScalarView.Index 一定也是采用了類似的設計。

事實上,不同視圖的 Index 是可以相互轉換的,使用 samePosition(in) 方法。具體的代碼如下:

let hearts = "Hearts <3 ?? :cupid:"
if let i = hearts.characters.index(of: " ") {
     let j = i.samePosition(in: hearts.utf8)
     print(Array(hearts.utf8.prefix(upTo: j)))
}
// Prints "[72, 101, 97, 114, 116, 115]"

調用 CharacterView 的下標方法返回的是一個 Character 類型的對象, 并且通過 UnicodeScalars來進行構造的。

關于 Character 類型, Character 可以由一個或多個 Unicode 組成, Character 是一個可以直接展示給用戶的字符,對于用戶來說,它是一個字符, 但是很多情況下,它實際上由多個 Unicode 字符組成。

Character 比較重要的一個屬性是 _representation ,這是一個枚舉類型,

@_versioned
internal enum Representation {
// A _StringBuffer whose first grapheme cluster is self.
// NOTE: may be more than 1 Character long.
case large(_StringBuffer._Storage)
case small(Builtin.Int63)
}

所有的 UTF-8 字符都可以用63位的Int來表示,也就是大部分情況下就是 small, 但是非 UTF-8就會使用 large(_StringBuffer._Storage) 來表示,它完全有可能超過 1 個字符。

CharacterView 同時還實現了協議 RangeReplaceableCollection , 可以從字面理解,這是一個代表 可以使用 Range 來替換部分的 Collection, 我們不做深究。

通過閱讀 CharacterView 的源碼,可以發現幾乎所有的操作都是借助于 UnicodeScalarView 來做的, 就不得不去看看 UnicodeScalarView 的對應操作。

<< struct >> String.UnicodeScalarView

我們可以猜測 UnicodeScalarView 應該擁有和 CharacterView 類似的功能, 是一個 BidirectionalCollection 和一個 RangeReplaceableCollection ,打開 StringUnicodeScalarView.swift, 的確,它實現了這兩個協議,而且它也同樣持有一個和對應 String相同的_StringCore,但是具體的實現和我的猜測有所出入。

我們只講它和 String.CharacterView 不同的地方, 從表現上我們已經知道, UnicodeScalarView 代表的是 UTF-32, 而一個 Character 可能由多個 UTF-32 組成。

從實現上, CharacterView 的相關集合、下標操作都是依賴于 UnicodeScalarView 的, 并且 CharacterView.Index 也是通過 UnicodeScarlarView.Index 來進行相關操作,但是 UnicodeScalarView.Index 并不是我所預想的依賴于 UTF16View.Index , 而是依賴于 _StringCore。

UnicodeScalarView.Index 僅有一個 Int 類型的屬性 _position 即位置,這也很好理解,因為每個 UnicodeScalar 的長度是固定的,即32位,所以一個位置足矣,自然也就不需要依賴于其他的 View .

UnicodeScalarView 中定義了一個迭代器 _ScratchIterator 負責對 _core 進行迭代,從而完成 UnicodeScalarView 的下標操作, 可以想象一下,對于 _core的 baseAddress 做對應的位操作就可以獲取到了。

同 CharacterView 和 Character 一樣, UnicodeScalarView 也有自己的 UnicodeScalar ,實際上就是一個 UTF-32。

在 StringUnicodeScalarView.swift 中還定義了關于 UnicodeScalarView.Index 和其他視圖的 Index 相互轉換的擴展方法。

其他視圖

UTF16View 和 UnicodeScalarView 類似,值得一提的是, _StringCore 本身提供了下標方法獲取 UTF16.CodeUnit,而并沒有擴展更多其他的下標,所以對于 UTF16View 來說,可以基于 _core 的下標操作來完成對應的集合相關操作。

UTF8View 對 _StringCore 進行了擴展,定義了一個 _UTF8Chunk 的別名(實際上是一個UInt64),即一個 UTF8 塊, UTF8.Index 中除了有一個 Int 類型的 coreIndex 外,直接存了一個 _UTF8Chunk 的 Buffer ,(索引里直接存值真是有創意,但是對于 UTF8 來說似乎是個好主意)

其他擴展

  • StringBridge 定義了和 NSString 之間的橋接
  • StringComparable 定義了針對 String 的比較方法,可以發現時基于 ASCII 碼的
  • StringHashable 完成了 String 的哈希策略
  • StringIndexConversions 定義了 String 不同 View 的 Index 的相互轉換
  • StringLegancy 定義了 String 的一些擴展方法,比如 hasPrefix、hasSuffix、repeat的初始化方法等等
  • 等等等等

StringBuffer

關于 StringBuffer , 其實這對于理解 String 的內存管理機制比較重要,簡單看了下, StringBuffer 主要是一個屬性—— _Storage ,他是一個 HeapBuffer , 關于 HeapBuffer ,又有很多東西要講,我準備放到下次再來研究下 StringBuffer

總結

先上一張圖:

圖中描述了 關于 String 的一些比較關鍵的關系,包括協議、結構體,省略了一些其他的協議,比如 Equatable,Comparable 等等,也省略了部分視圖(UTF16、UTF8)(都畫上要亂套了)

首先 String 是一個結構非常簡單的結構體,只持有一個 _StringCore 的結構體,于是關于 String 的所有內容都封裝在 _StringCore 上, 這是一個比較好的設計, _StringCore 是一個私有對象,直接隔離了 String 的所有內部結構(使用者并不關心),這樣既安全又方便擴展。

_StringCore 包含三個屬性, _baseAddress ( 指針地址,指向堆中的目標字符串) _countAndFlags (字符串長度), _owner 用途位置,希望在下一次看 StringBuffer 的時候能夠看出端倪,目前線索就指向 StringBuffer 。

關于 String 的內存回收機制:copy-and-write、基于堆,重復利用。 在下一章 StringBuffer 中應該會對這塊機制做更進一步的了解。

String 視圖, String 有 4 個視圖, CharacterView UnicodeScalarView UTF16View UTF8View , String 并不持有視圖,每次調用的時候會重新創建視圖(復制_core),不同的視圖擁有對應類型的索引(Index),索引之間可以相互轉換, 視圖是 Collection 可遍歷、可取下標,而 String 不行。

 

來自:http://blog.luckymore.wang/2016/12/08/Read-stdlib-of-swift3-0-String/

 

 本文由用戶 EsperanzaK5 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!