亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Swift中的指針操作詳解

 更新時間:2017年01月19日 11:51:16   作者:Swiftyper  
從傳統(tǒng)的C代碼和與之無縫配合的Objective-C代碼遷移到Swift并非小工程,我們的代碼庫肯定會時不時出現(xiàn)一些和C協(xié)作的地方,如果想要繼續(xù)使用那些C API的話,了解一些基本的Swift指針操作和使用的知識會很有幫助。下面通過這篇文章一起來學習下吧。

前言

Objective-C和C語言經(jīng)常需要使用到指針。Swift中的數(shù)據(jù)類型由于良好的設(shè)計,使其可以和基于指針的C語言API無縫混用。但是語法上有很大的差別。

默認情況下,Swift 是內(nèi)存安全的,這意味著它禁止我們直接操作內(nèi)存,并且確保所有的變量在使用前都已經(jīng)被正確地初始化了。但是,Swift 也提供了我們使用指針直接操作內(nèi)存的方法,直接操作內(nèi)存是很危險的行為,很容易就出現(xiàn)錯誤,因此官方將直接操作內(nèi)存稱為 “unsafe 特性”。

一旦我們開始直接操作內(nèi)存,一切就得靠我們自己了,因為在這種情況下編譯能給我們提供的幫助實在不多。正常情況下,我們在與 C 進行交互,或者我們需要挖掘 Swift 內(nèi)部實現(xiàn)原理的時候會需要使用到這個特性。

Memory Layout

Swift 提供了 MemoryLayout 來檢測特定類型的大小以及內(nèi)存對齊大?。?/p>

MemoryLayout<Int>.size // return 8 (on 64-bit)
MemoryLayout<Int>.alignment // return 8 (on 64-bit)
MemoryLayout<Int>.stride // return 8 (on 64-bit)
MemoryLayout<Int16>.size // return 2
MemoryLayout<Int16>.alignment // return 2
MemoryLayout<Int16>.stride // return 2
MemoryLayout<Bool>.size // return 2
MemoryLayout<Bool>.alignment // return 2
MemoryLayout<Bool>.stride // return 2
MemoryLayout<Float>.size // return 4
MemoryLayout<Float>.size // return 4
MemoryLayout<Float>.alignment // return 4
MemoryLayout<Double>.stride // return 8
MemoryLayout<Double>.alignment // return 8
MemoryLayout<Double>.stride // return 8

MemoryLayout<Type> 是一個用于在編譯時計算出特定類型(Type)的 size, alignment 以及 stride 的泛型類型。返回的數(shù)值以字節(jié)為單位。例如 Int16 類型的大小為 2 個字節(jié),內(nèi)存對齊為 2 個字節(jié)以及當我們需要連續(xù)排列多個 Int16 類型時,每一個 Int16 所需要占用的大小(stride)為 2 個字節(jié)。所有基本類型的 stride 都與 size 是一致的。

接下來,看看結(jié)構(gòu)體類型的 MemoryLayout:

struct EmptyStruct {}
MemoryLayout<EmptyStruct>.size // returns 0
MemoryLayout<EmptyStruct>.alignment // returns 1
MemoryLayout<EmptyStruct>.stride // returns 1
struct SampleStruct {
 let number: UInt32
 let flag: Bool
}
MemoryLayout<SampleStruct>.size // returns 5
MemoryLayout<SampleStruct>.alignment // returns 4
MemoryLayout<SampleStruct>.stride // returns 8

空結(jié)構(gòu)體的大小為 0,內(nèi)存對齊為 1, 表明它可以存在于任何一個內(nèi)存地址上。有趣的是 stride 為 1,這是因為盡管結(jié)構(gòu)為空,但是當我們使用它創(chuàng)建一個實例的時候,它也必須要有一個唯一的地址。

對于 SampleStruct,它所占的大小為 5,但是 stride 為 8。這是因為編譯需要為其填充空白的邊界,使其符合它的 4 字節(jié)內(nèi)存邊界對齊。

再來看看類:

class EmptyClass {}
MemoryLayout<EmptyClass>.size // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.alignment // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.stride // returns 8 (on 64-bit)
class SampleClass {
 let number: Int64 = 0
 let flag: Bool = false
}
MemoryLayout<SampleClass>.size // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.aligment // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.stride // returns 8 (on 64-bit)

由于類都是引用類型,所以它所有的大小都是 8 字節(jié)。

關(guān)于 MemoryLayout 的更多詳細信息可以參考 Mike Ash 的演講

指針

一個指針就是對一個內(nèi)存地址的封裝。在 Swift 當中直接操作指針的類型都有一個 “unsafe” 前綴,所以它的指針類型稱為 UnsafePointer。這個前綴似乎看起來很令人惱火,不過這是 Swift 在提醒你,你現(xiàn)在正在跨越雷池,編譯器不會對這種操作進行檢查,你需要對自己的代碼承擔全部的責任。

Swift 中包含了一打類型的指針類型,每個類型都有它們的作用和目的,使用適當?shù)闹羔橆愋涂梢苑乐瑰e誤的發(fā)生并且更清晰地表達開發(fā)者的意圖,防止未定義行為的產(chǎn)生。

Swift 的指針類型使用了很清晰的命名,我們可以通過名字知道這是一個什么類型的指針??勺兓蛘卟豢勺?,原生(raw)或者有類型的,是否是緩沖(buffer)類型,這三種特性總共組合出了 8 種指針類型。

接下來的幾個小節(jié)會詳細介紹這幾種指針類型。

使用原生(Raw)指針

在 Playground 中添加如下代碼:

// 1
let count = 2
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let byteCount = stride * count
 
// 2
do {
 print("Raw pointers")
 
 // 3
 let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 // 4
 defer {
 pointer.deallocate(bytes: byteCount, alignedTo: alignment)
 }
 
 // 5
 pointer.storeBytes(of: 42, as: Int.self)
 pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
 pointer.load(as: Int.self)
 pointer.advanced(by: stride).load(as: Int.self)
 
 // 6
 let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
 for (index, byte) in bufferPointer.enumerated() {
 print("byte \(index): \(byte)")
 }
}

在這個代碼段中,我們使用了 Unsafe Swift 指針去存儲和讀取兩個整型數(shù)值。

接下來是對這段代碼的解釋:

1、聲明了接下來都會用到的幾個常量:

  • count 表示了我們要存儲的整數(shù)的個數(shù)
  • stride 表示了 Int 類型的 stride
  • alignment 表示了 Int 類型的內(nèi)存對齊
  • byteCount 表示占用的全部字節(jié)數(shù)

2、使用 do 來增加一個作用域,讓我們可以在接下的示例中復用作用域中的變量名

3、使用 UnsafeMutableRawPointer.allocate 方法來分配所需的字節(jié)數(shù)。我們使用了 UnsafeMutableRawPointer,它的名字表明這個指針可以用來讀取和存儲(改變)原生的字節(jié)。

4、使用 defer 來保證內(nèi)存得到正確地釋放。操作指針的時候,所有內(nèi)存都需要我們手動進行管理。

5、storeBytes load 方法用于存儲和讀取字節(jié)。第二個整型數(shù)值的地址通過對 pointer 的地址前進 stride 來得到。因為指針類型是 Strideable 的,我們也可以直接使用指針算術(shù)運算 (pointer+stride).storeBytes(of: 6, as: Int.self)。

6、UnsafeRawBufferPointer 類型以一系列字節(jié)的形式來讀取內(nèi)存。這意味著我們可以這些字節(jié)進行迭代,對其使用下標,或者使用 filter,map 以及 reduce 這些很酷的方法。緩沖類型指針使用了原生指針進行初始化。

使用類型指針

我們可以使用類型指針實現(xiàn)跟上面代碼一樣的功能,并且更簡單:

do {
 print("Typed pointers")
 
 let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
 pointer.initialize(to: 0, count: count)
 defer {
 pointer.deinitialize(count: count)
 pointer.deallocate(capacity: count)
 }
 
 pointer.pointee = 42
 pointer.advanced(by: 1).pointee = 6
 pointer.pointee
 pointer.advanced(by: 1).pointee
 
 let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
 for (index, value) in bufferPointer.enumerated() {
 print("value \(index): \(value)")
 }
}

注意到以下幾點不同:

  • 我們使用了 UnsafeMutablePointer.allocate 進行內(nèi)存的分配。指定的泛型參數(shù)讓 Swift 知道我們將會使用這個指針來存儲和讀取 Int 類型的值。
  • 在使用類型指針前需要對其進行初始化,并在使用后銷毀。這兩個功能分別是使用 initialize deinitialize 方法。
  • 類型指針提供了 pointee 屬性,它可以以類型安全的方式讀取和存儲值。
  • 當需要指針前進的時候,我們只需要指定想要前進的個數(shù)。類型指針會自動根據(jù)它所指向的數(shù)值類型來計算 stride 值。同樣的,我們可以直接對指針進行算術(shù)運算 (pointer + 1).pointee = 6
  • 有類型的緩沖型指針也會直接操作數(shù)值,而非字節(jié)

將原生指針轉(zhuǎn)換為類型指針

類型指針并不總是使用初始化得到的,它們可以從原生指針中轉(zhuǎn)化而來。

在 Playground 中添加如下代碼:

do {
 print("Converting raw pointers to typed pointers")
 
 let rawPointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 defer {
 rawPointer.deallocate(bytes: byteCount, alignedTo: alignment)
 }
 
 let typedPointer = rawPointer.bindMemory(to: Int.self, capacity: count)
 typedPointer.initialize(to: 0, count: count)
 defer {
 typedPointer.deinitialize(count: count)
 }
 
 typedPointer.pointee = 42
 typedPointer.advanced(by: 1).pointee = 6
 typedPointer.pointee
 typedPointer.advanced(by: 1).pointee
 
 let bufferPointer = UnsafeBufferPointer(start: typedPointer, count: count)
 for (index, value) in bufferPointer.enumerated() {
 print("value \(index): \(value)")
 }
}

這段代碼與上一段類似,除了它先創(chuàng)建了原生指針。我們通過將內(nèi)存綁定(binding)到指定的類型上來創(chuàng)建類型指針。通過對內(nèi)存的綁定,我們可以通過類型安全的方法來訪問它。將我們手動創(chuàng)建類型指針的時候,系統(tǒng)其實自動幫我們進行了內(nèi)存綁定。

獲取一個實例的字節(jié)

很多時候我們需要從一個現(xiàn)存的實例里獲取它的字節(jié)。這時可以使用 withUnsafeBytes(of:) 方法。

在 Playground 中添加如下代碼:

do {
 print("Getting the bytes of an instance")
 
 var sampleStruct = SampleStruct(number: 25, flag: true)
 
 withUnsafeBytes(of: &sampleStruct) { bytes in
 for byte in bytes {
 print(byte)
 }
 }
}

這段代碼會打印出 SampleStruct 實例的原生字節(jié)。withUnsafeBytes(of:) 方法可以獲取到 UnsafeRawBufferPointer并傳入閉包中供我們使用。

withUnsafeBytes 同樣適合用 Array Data 的實例。

使用 Swift 操作指針的三大原則

當我們使用 Swift 操作指針的時候必須加倍小心,防止寫出未定義行為的代碼。下面是幾個壞代碼的示例。

不要從 withUnsafeBytes 中返回指針

 // Rule #1
do {
 print("1. Don't return the pointer from withUnsafeBytes!")
 
 var sampleStruct = SampleStruct(number: 25, flag: true)
 
 let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
 return bytes // strange bugs here we come ☠️☠️☠️
 }
 
 print("Horse is out of the barn!", bytes) /// undefined !!!
}

絕對不要讓指針逃出 withUnsafeBytes(of:) 的作用域范圍。這樣的代碼會成為定時炸彈,你永遠不知道它什么時候可以用,而什么時候會崩潰。

一次只綁定一種類型

// Rule #2
do {
 print("2. Only bind to one type at a time!")
 
 let count = 3
 let stride = MemoryLayout<Int16>.stride
 let alignment = MemoryLayout<Int16>.alignment
 let byteCount = count * stride
 
 let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 
 let typedPointer1 = pointer.bindMemory(to: UInt16.self, capacity: count)
 
 // Breakin' the Law... Breakin' the Law (Undefined behavior)
 let typedPointer2 = pointer.bindMemory(to: Bool.self, capacity: count * 2)
 
 // If you must, do it this way:
 typedPointer1.withMemoryRebound(to: Bool.self, capacity: count * 2) {
 (boolPointer: UnsafeMutablePointer<Bool>) in
 print(boolPointer.pointee) // See Rule #1, don't return the pointer
 }
}
**絕對不要**讓一個內(nèi)存同時綁定兩個不同的類型。如果你需要臨時這么做,可以使用 `withMemoryRebound(to:capacity:)` 來對內(nèi)存進行重新綁定。并且,這條規(guī)則也表明了不要將一個基本類型(如 Int)重新綁定到一個自定義類型(如 class)上。不要做這種傻事。
### 不要操作超出范圍的內(nèi)存
```swift
// Rule #3... wait
do {
 print("3. Don't walk off the end... whoops!")
 
 let count = 3
 let stride = MemoryLayout<Int16>.stride
 let alignment = MemoryLayout<Int16>.alignment
 let byteCount = count * stride
 
 let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount + 1) // OMG +1????
 
 for byte in bufferPointer {
 print(byte) // pawing through memory like an animal
 }
}

這是最糟糕的一種錯誤了,請再三檢查你的代碼,保證不要有這種情況出現(xiàn)。切記。

示例:隨機數(shù)生成

隨機數(shù)在很多地方都有重要的作用,從游戲到機器學習。macOS 提供了 arc4random 方法用于隨機數(shù)生成。不幸的是,這個方法無法在 Linux 上使用。并且,arc4random 方法只提供了 UInt32 類型的隨機數(shù)。事實上,/dev/urandom 這個設(shè)備文件中就提供了無限的隨機數(shù)。

這一小節(jié)中,我們將使用指針讀取這個文件,并產(chǎn)生完全類型安全的隨機數(shù)。

創(chuàng)建一個新 Playground,命名為 RandomNumbers,并確保選擇了 macOS 平臺。

創(chuàng)建完成后,添加如下代碼:

import Foundation
 
enum RandomSource {
 
 static let file = fopen("/dev/urandom", "r")!
 static let queue = DispatchQueue(label: "random")
 
 static func get(count: Int) -> [Int8] {
 let capacity = count + 1 // fgets adds null termination
 var data = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
 defer {
 data.deallocate(capacity: capacity)
 }
 queue.sync {
 fgets(data, Int32(capacity), file)
 }
 return Array(UnsafeMutableBufferPointer(start: data, count: count))
 }
}

為了確保整個系統(tǒng)中只存在一個 file 變量,我們對其使用了 static 修飾符。系統(tǒng)會在我們的進程結(jié)束時關(guān)閉文件。因為我們有可能在多個線程中同時獲取隨機數(shù),所以需要使用一個串行的 GCD 隊列來進行保護。

get 函數(shù)是所有功能完成的地方。首先,我們根據(jù)傳入的大小分配了必要的內(nèi)存,注意這里需要 +1 是因為 fets 函數(shù)總是以 \0 結(jié)束。接下來,我們就使用 fgets 函數(shù)從文件中讀取數(shù)據(jù),確保我們在串行隊列中進行讀取操作。最后,我們先將數(shù)據(jù)封裝為一個 UnsafeMutableBufferPointer,并將其轉(zhuǎn)化為一個數(shù)組。

在 playground 的最后添加如下代碼:

extension Integer {
 
 static var randomized: Self {
 let numbers = RandomSource.get(count: MemoryLayout<Self>.size)
 return numbers.withUnsafeBufferPointer { bufferPointer in
 return bufferPointer.baseAddress!.withMemoryRebound(to: Self.self, capacity: 1) {
 return $0.pointee
 }
 }
 }
 
}
 
Int8.randomized
UInt8.randomized
Int16.randomized
UInt16.randomized
Int16.randomized
UInt32.randomized
Int64.randomized
UInt64.randomized

這里我們?yōu)?Integer 協(xié)議添加了一個靜態(tài)屬性,并為其提供了默認實現(xiàn)。我們首先獲取了隨機數(shù),隨后我們將獲得字節(jié)數(shù)組重新綁定為所需要的類型,然后返回它的值。簡單!

就這樣,我們使用 unsafe Swift 實現(xiàn)了一個類型安全的隨機器生成方法。

在日常開發(fā)中,我們并不會接觸到很多直接操作內(nèi)存的情境。但是掌握它的操作,能讓我們在碰到類似代碼里更加從容。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。

相關(guān)文章

  • Swift實現(xiàn)Selection Sort選擇排序算法的實例講解

    Swift實現(xiàn)Selection Sort選擇排序算法的實例講解

    選擇排序是一種穩(wěn)定的排序算法,且實現(xiàn)代碼通常比冒泡排序要來的簡單,這里我們就來看一下Swift實現(xiàn)Selection Sort選擇排序的實例講解
    2016-07-07
  • RxSwift發(fā)送及訂閱 Subjects、Variables代碼示例

    RxSwift發(fā)送及訂閱 Subjects、Variables代碼示例

    這篇文章主要介紹了RxSwift發(fā)送及訂閱 Subjects、Variables代碼示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-12-12
  • swift 字符串String的使用方法

    swift 字符串String的使用方法

    這篇文章主要介紹了swift 字符串String的使用方法的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Objective-C和Swift的轉(zhuǎn)換速查手冊(推薦)

    Objective-C和Swift的轉(zhuǎn)換速查手冊(推薦)

    這篇文章主要給大家介紹了關(guān)于Objective-C和Swift的轉(zhuǎn)換速查手冊的相關(guān)資料,文中通過示例代碼介紹的非常詳細,非常推薦給大家參考學習使用,需要的朋友們下面隨著小編來一起學習學習不
    2018-06-06
  • Swift仿微信語音通話最小化時后的效果實例代碼

    Swift仿微信語音通話最小化時后的效果實例代碼

    這篇文章主要介紹了Swift仿微信語音通話最小化時后的效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • 詳解Swift面向?qū)ο缶幊讨械姆椒?method)

    詳解Swift面向?qū)ο缶幊讨械姆椒?method)

    既然面向?qū)ο竽蔷鸵欢〞衜ethod,方法和面向過程語言中的function函數(shù)并沒什么區(qū)別,只不過方法在面向?qū)ο笳Z言中可以被類來約束作用域,這里我們就來詳解Swift面向?qū)ο缶幊讨械姆椒?method)
    2016-07-07
  • 利用Swift如何計算文本的size示例詳解

    利用Swift如何計算文本的size示例詳解

    這篇文章主要給大家介紹了關(guān)于利用Swift如何計算文本的size的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對各位iOS開發(fā)者們的工作或者學習具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2017-11-11
  • Swift中常量和變量的區(qū)別與聲明詳解

    Swift中常量和變量的區(qū)別與聲明詳解

    Swift語言同樣和Java和OC等語言一樣是同樣是需要聲明常量和變量的,下面就讓我們來學習一下Swift的常量和變量。這篇文章主要給大家介紹了關(guān)于Swift中常量和變量的區(qū)別與聲明的相關(guān)資料,需要的朋友可以參考下。
    2017-11-11
  • Swift中添加雙擊手勢識別器

    Swift中添加雙擊手勢識別器

    在這次IOS應用開發(fā)教程中,我們打算實現(xiàn)手勢識別。正如你所知道的,IOS支持大量的手勢操作,它們能提供了很好的應用控制和出色用戶體驗。
    2019-08-08
  • Swift編程中用以管理內(nèi)存的自動引用計數(shù)詳解

    Swift編程中用以管理內(nèi)存的自動引用計數(shù)詳解

    這篇文章主要介紹了Swift編程中用以管理內(nèi)存的自動引用計數(shù)詳解,是Swift入門學習中的基礎(chǔ)知識,需要的朋友可以參考下
    2015-11-11

最新評論