Swift 指针

Swift 指针

前言

指针,作为编程中最重要的概念,一直存在于各大语言中,下面我们就来探索一下Swift中的指针。

1. Swift 指针简介

Swift中的指针分为两类:

  • raw pointer:未指定数据类型的指针(原生指针),在Swift中的表示是UnsafeRawPointer,我们只知道这是个指针,并不知道其内部存储的类型
  • typed pointer:指定数据类型的指针,在Swift中的表示是UnsafePointer<T>,其中T表示泛型,指针内部存储的是T类型的数据

swift与OC指针的对比:

Swift Objective-C 说明
unsafePointer<T> const T * 指针及所指向的内容都不可变
UnsafeMutablePointer<T> T * 指针及其所指向的内存内容均可变
UnsafeRawPointer const void * 指针指向不可变未知类型
UnsafeMutableRawPointer void * 指针指向可变未知类型

2. raw pointer简单使用

2.1 示例

注: 对于raw pointer首先要说明一下,其内存管理是手动管理的,指针在使用完毕后需要手动释放

举个例子:

// 定义一个未知类型的的指针p,分配32字节大小的空间,8字节对齐
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

// 存储数据
for i in 0..<4 {
    p.storeBytes(of: i + 1, as: Int.self)
}

// 读取数据
for i in 0..<4 {
    // 从首地址开始偏移读取
    let value = p.load(fromByteOffset: i * 8, as: Int.self)
    print("index: \(i) value: \(value)")
}

// 释放内存
p.deallocate()

运行结果:

image

从运行结果中可以看到,这并不是我们想要的结果,这也是我们平常在使用的时候需要特别注意的。因为我们在读取的时候是从指针首地址进行不断的偏移读取的,但是存储的时候却都是存储在了首地址,所以存储的时候也要进行偏移。修改后的代码如下:

// 定义一个未知类型的的指针p,分配32字节大小的空间,8字节对齐
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

// 存储数据
for i in 0..<4 {
//    p.storeBytes(of: i + 1, as: Int.self)
    // 修改后(每次存储的位置都有增加,也就是偏移)
    p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}

// 读取数据
for i in 0..<4 {
    // 从首地址开始偏移读取
    let value = p.load(fromByteOffset: i * 8, as: Int.self)
    print("index: \(i) value: \(value)")
}

// 释放内存
p.deallocate()

打印结果:

image

2.2 allocate 源码

我们来看看allocate函数的源码:(在UnsafeRawPointer.Swift文件中)

/// Allocates uninitialized memory with the specified size and alignment.
///
/// You are in charge of managing the allocated memory. Be sure to deallocate
/// any memory that you manually allocate.
///
/// The allocated memory is not bound to any specific type and must be bound
/// before performing any typed operations. If you are using the memory for
/// a specific type, allocate memory using the
/// `UnsafeMutablePointer.allocate(capacity:)` static method instead.
///
/// - Parameters:
///   - byteCount: The number of bytes to allocate. `byteCount` must not be negative.
///   - alignment: The alignment of the new region of allocated memory, in
///     bytes.
/// - Returns: A pointer to a newly allocated region of memory. The memory is
///   allocated, but not initialized.
@inlinable
public static func allocate(
byteCount: Int, alignment: Int
) -> UnsafeMutableRawPointer {
// For any alignment <= _minAllocationAlignment, force alignment = 0.
// This forces the runtime's "aligned" allocation path so that
// deallocation does not require the original alignment.
//
// The runtime guarantees:
//
// align == 0 || align > _minAllocationAlignment:
//   Runtime uses "aligned allocation".
//
// 0 < align <= _minAllocationAlignment:
//   Runtime may use either malloc or "aligned allocation".
var alignment = alignment
if alignment <= _minAllocationAlignment() {
  alignment = 0
}
return UnsafeMutableRawPointer(Builtin.allocRaw(
    byteCount._builtinWordValue, alignment._builtinWordValue))
}

该方法就是:

  • 以指定的大小和对齐方式分配未初始化的内存
  • 首先对对齐方式进行校验
  • 然后调用Builtin.allocRaw方法进行分配内存
  • BuiltinSwift的标准模块,可以理解为调用(匹配)LLVM中的方法

3. typed pointer

在前几篇文章中打印内存地址的时候就用过withUnsafePointer,下面我们就来看看。

3.1 定义

/// Invokes the given closure with a pointer to the given argument.
///
/// The `withUnsafePointer(to:_:)` function is useful for calling Objective-C
/// APIs that take in parameters by const pointer.
///
/// The pointer argument to `body` is valid only during the execution of
/// `withUnsafePointer(to:_:)`. Do not store or return the pointer for later
/// use.
///
/// - Parameters:
///   - value: An instance to temporarily use via pointer.
///   - body: A closure that takes a pointer to `value` as its sole argument. If
///     the closure has a return value, that value is also used as the return
///     value of the `withUnsafePointer(to:_:)` function. The pointer argument
///     is valid only for the duration of the function's execution.
///     It is undefined behavior to try to mutate through the pointer argument
///     by converting it to `UnsafeMutablePointer` or any other mutable pointer
///     type. If you need to mutate the argument through the pointer, use
///     `withUnsafeMutablePointer(to:_:)` instead.
/// - Returns: The return value, if any, of the `body` closure.
@inlinable public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result

该函数一共两个参数:

  • 第一个就是要获取其指针的变量
  • 第二个是一个闭包,然后通过rethrows关键字重新抛出Result(也就是闭包表达式的返回值),闭包的参数和返回值都是泛型,关于这种写法可以缩写,详见后面的代码。

3.2 简单使用

3.2.1 常见用法

示例代码:

var a = 10

/**
    通过Swift提供的简写的API,这里是尾随闭包的写法
    返回值的类型是 UnsafePointer<Int>
 */
let p = withUnsafePointer(to: &a) { $0 }
print(p)

withUnsafePointer(to: &a) {
    print($0)
}

// Declaration let p1:UnsafePointer<Int>
let p1 = withUnsafePointer(to: &a) { ptr in
    return ptr
}
print(p1)

打印结果:

image

以上三种用法是我们最常用的三种方法,都能够打印出变量的指针。那么是否可以通过指针修改变量的值呢?下面我们就来研究一下:

3.2.2 通过指针获取变量值

要想改变值,首先就要能够访问到变量的值:

let p = withUnsafePointer(to: &a) { $0 }
print(p.pointee)

withUnsafePointer(to: &a) {
    print($0.pointee)
}

let p1 = withUnsafePointer(to: &a) { ptr in
    return ptr
}
print(p1.pointee)

let p2 = withUnsafePointer(to: &a) { ptr in
    return ptr.pointee
}
print(p2)

打印结果:

image

3.2.2 通过指针修改变量值:

如果使用的是withUnsafePointer是不能直接在闭包中修改指针的:

image

但是我们可以通过间接的方式,通过返回值修改,给原来变量赋值的方式修改(其实这种方式很low)

a = withUnsafePointer(to: &a){ ptr in
    return ptr.pointee + 2
}

print(a)

打印结果:

image

我们可以使用withUnsafeMutablePointer,直接修改变量的值。

withUnsafeMutablePointer(to: &a){ ptr in
    ptr.pointee += 2
}

打印结果:

image

还有另一种方式,就是通过创建指针的方式,这也是一种创建Type Pointer的方式:

// 创建一个指针,指针内存存储的是Int类型数据,开辟一个8*1字节大小的区域
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)

//初始化指针
ptr.initialize(to: a)
// 修改
ptr.pointee += 2

print(a)
print(ptr.pointee)

// 反初始化,与下面的代码成对调用,管理内存
ptr.deinitialize(count: 1)
// 释放内存
ptr.deallocate()
image

从这里我们可以看到,指针的值在修改后是变了的,但是原变量的值并没有改变。所以不能用于直接修改原变量。

4. 实战案例

4.1 案例一

本案例是初始化一个指针,能够访问两个结构体实例对象。

首先定义一个结构体

struct Teacher {
    var age = 18
    var height = 1.65
}

下面我们通过三种方式访问指针中的结构体对象

  1. 通过下标访问
  2. 通过内存平移访问
  3. 通过successor()函数访问
// 分配两个Teacher大小空间的指针
let ptr = UnsafeMutablePointer<Teacher>.allocate(capacity: 2)

// 初始化第一个Teacher
ptr.initialize(to: Teacher())
// 初始化第二个Teacher
ptr.successor().initialize(to: Teacher(age: 20, height: 1.85))
// 错误的初始化方式,因为这是确定类型的指针,只需移动一步即移动整个类型大小的内存
//ptr.advanced(by: MemoryLayout<Teacher>.stride).initialize(to: Teacher(age: 20, height: 1.85))

// 通过下标访问
print(ptr[0])
print(ptr[1])

// 内存偏移
print(ptr.pointee)
print((ptr+1).pointee)

// successor
print(ptr.pointee)
print(ptr.successor().pointee)

// 反初始化,释放内存
ptr.deinitialize(count: 2)
ptr.deallocate()

打印结果:

image

这里有一点需要特别注意:

我们在初始化的时候,如果需要在内存后面继续初始化,平移的时候,对于已知类型的指针,每平移一步就是已知类型所占内存大小。如果按照上面内存的写法就是16*16个字节的大小,移动了16个已知类型数据的内存大小,这样就跟我们实际的需求不符了。

4.2 案例二

我们知道在Swift中对象的本质是HeapObject,下面我们就通过内存的方式,将实例对象绑定到我们自定义的HeapObject上。

首先定义如下代码:

struct HeapObject {
    var kind: UnsafeRawPointer
    var strongRef: UInt32
    var unownedRef: UInt32
}

class Teacher {
    var age: Int = 18
}

var t = Teacher()

指针绑定:

/**
 使用withUnsafeMutablePointer获取到的指针是UnsafeMutablePointer<T>类型
 UnsafeMutablePointer<T>没有bindMemory方法
 所以此处引入Unmanaged
 */
//let ptr = withUnsafeMutablePointer(to: &t) { $0 }

/**
 Unmanaged 指定内存管理,类似于OC与CF交互时的所有权转换__bridge
 Unmanaged 有两个函数:
 - passUnretained:不增加引用计数,也就是不获得所有权
 - passRetained:   增加引用计数,也就是可以获得所有权
 以上两个函数,可以通过toOpaque函数返回一个
 UnsafeMutableRawPointer 指针
*/
let ptr = Unmanaged.passUnretained(t).toOpaque()
//let ptr = Unmanaged.passRetained(t).toOpaque()

/**
 bindMemory :将指针绑定到指定类型数据上
 如果没有绑定则绑定
 已绑定则重定向到指定类型上
*/
let h = ptr.bindMemory(to: HeapObject.self, capacity: 1)

print(h.pointee)

运行结果:

image

4.3 案例三

既然可以绑定对象,那么我们也就可以绑定类,接着案例二中的一些代码,kind对应的就是metaData

首先我们将swift中的类结构定义成一个内存结构一致的结构体:

struct swiftClass {
    var kind: UnsafeRawPointer
    var superClass: UnsafeRawPointer
    var cachedata1: UnsafeRawPointer
    var cachedata2: UnsafeRawPointer
    var data: UnsafeRawPointer
    var flags: UInt32
    var instanceAddressOffset: UInt32
    var instanceSize: UInt32
    var flinstanceAlignMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressOffset: UInt32
    var description: UnsafeRawPointer
}

然后通过使用案例二中的kind,绑定成上面的结构体。

image

4.4 案例四

在实际开发中,我们往往会调用其他人的api完成一些代码,在指针操作过程中,往往会因为类型不匹配造成传参问题,下面我们就来看一个例子:

首先我们定义一个打印指针的函数:

func printPointer(p: UnsafePointer<Int>) {
    print(p)
    print("end")
}

此时我们在定义一个元组,我们要打印元组指针,或者通过指针获取一些数据,按照以前的写法就会报如下编译错误:

image

那么该如何解决该问题呢?首先不能修改函数,因为这个函数是通用的,也不一定都是我们自己定义的。此时我们通过强转和assumingMemoryBound来解决这个问题,示例代码:

withUnsafeMutablePointer(to: &tul) { tulPtr in
    printPointer(p: UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}

lldb打印结果:


image

assumingMemoryBound是假定内存绑定,目的是告诉编译器已经绑定过Int类型了,不需要在检查内存绑定。

那么我们将元组换成结构体呢?我们此时想要获取结构体中的属性。

struct Test {
    var a: Int = 10
    var b: Int = 20
}

var t = Test()

withUnsafeMutablePointer(to: &t) { ptr in
    printPointer(p: UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
}

其实是没有任何区别的

打印结果:

image

那么如果我想想获取结构体中的属性呢?

withUnsafeMutablePointer(to: &t) { ptr in
//    let strongRefPtr = withUnsafePointer(to: &ptr.pointee.b) { $0 }
//    printPointer(p: strongRefPtr)
//    let strongRefPtr = UnsafeRawPointer(ptr).advanced(by: MemoryLayout<Int>.stride)
    let strongRefPtr = UnsafeRawPointer(ptr) - MemoryLayout<Test>.offset(of: \Test.b)!
    printPointer(p: strongRefPtr.assumingMemoryBound(to: Int.self))
}

打印结果:

image

以上提供了三种方式实现访问结构体中的属性。

4.5 案例五

使用withMemoryRebound临时更改内存绑定类型,withMemoryRebound的主要应用场景还是处理一些类型不匹配的场景,将内存绑定类型临时修改成想要的类型,在闭包里生效,不会修改原指针的内存绑定类型。

var age: UInt64 = 18

let ptr = withUnsafePointer(to: &age) { $0 }
// 临时更改内存绑定类型
ptr.withMemoryRebound(to: Int.self, capacity: 1) { (ptr) in
    printPointer(p: ptr)
}

func printPointer(p: UnsafePointer<Int>) {
    print(p)
    print("end")
}

5. 总结

至此我们对Swift中指针的分析基本就完事了,现在总结如下:

  1. Swift中的指针分为两种:
    1. raw pointer:未指定类型(原生)指针,即:UnsafeRawPointerunsafeMutableRawPointer
    2. type pointer:指定类型的指针,即:UnsafePointer<T>UnsafeMutablePointer<T>
  2. 指针的绑定主要有三种方式:
    1. withMemoryRebound:临时更改内存绑定类型
    2. bingMemory(to: capacity:):更改内存绑定的类型,如果之前没有绑定,那么就是首次绑定,如果绑定过了,会被重新绑定为该类型
    3. assumingMemoryBound:假定内存绑定,这里就是告诉编译器,我就是这种类型,不要检查了(控制权由我们决定)
  3. 内存指针的操作都是危险的,使用时要慎重
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 为了避免疏漏, 我从官方文档作了截图, 苹果官网文档1 , 文档2 本文概要 按照官方文档, 介绍Swift中的指...
    Lin__Chuan阅读 2,652评论 12 9
  • 前言 本篇文章主要讲解一下Swift中的指针,以及相关的应用场景,指针也是面试官经常问到的知识点,希望大家能够掌握...
    深圳_你要的昵称阅读 3,031评论 0 11
  • 本文系学习Swift中的指针操作详解的整理 默认情况下Swift是内存安全的,苹果官方不鼓励我们直接操作内存。但是...
    流火绯瞳阅读 14,968评论 2 28
  • 参考文献 Swift结构体指针操作官方文档Swift 和 C 不得不说的故事Swift指针和托管,你看我就够了 W...
    沉静BBQ阅读 8,001评论 1 25
  • 指针 Swift中的指针分为两类, typed pointer指定数据类型指针,raw pinter未指定数据类型...
    Mjs阅读 538评论 0 1