前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >swift底层探索 06 - 指针简单使用swift底层探索 06 - 指针简单使用

swift底层探索 06 - 指针简单使用swift底层探索 06 - 指针简单使用

作者头像
用户8893176
发布2021-08-09 11:24:35
6530
发布2021-08-09 11:24:35
举报
文章被收录于专栏:小黑娃Henry

图一

如果在lldb中需要获取值类型的地址,直接使用po、p、v都是无法获取地址的,只能转为指针后才可以获取,如图一。

指针

Swift的指针分类两类:

  1. typed pointer指定类型指针:unsafePointer<T>,unsafeMutablePointer<T>
  2. raw pointer未指定类型指针:unsafeRawPointer,unsafeMutableRawPointer
Swift指针与OC指针类比

Swift

OC

unsafePointer<T>

const T *

指定类型指针与指针内存都不可变

unsafeMutablePointer<T>

T *

指定类型指针与指针内存都可变

unsafeRawPointer

const void *

未知类型指针与指针内存都不可变

unsafeMutableRawPointer

void *

未知类型指针与指针内存都可变

1. 未指定类型指针(raw pointer)

实例:
代码语言:javascript
复制
//获取Int的内存大小 : 8
let alignment = MemoryLayout<Int>.stride
//初始化 32字节的内存空间
//let只限制当前指针不允许更换指向,并不能限制其指向内存的修改
let rawPtr = UnsafeMutableRawPointer.allocate(byteCount: alignment * 4, alignment: alignment)
//指针赋值
for i in 0...3{
//    指针向前移动
    let tempPtr = rawPtr.advanced(by: i * alignment)
//    赋值
    tempPtr.storeBytes(of: i, as: Int.self)
    
//    赋值另一个综合API
//    rawPtr.storeBytes(of: i, toByteOffset: i * alignment, as: Int.self)
}
//指针读取,每次读取都需要进行偏移
for i in 0...3{
    print(rawPtr.load(fromByteOffset: i * alignment, as: Int.self))
}
//手动销毁
rawPtr.deallocate()

输出结果

  • unsafe顾名思义是不安全的,也就是从创建开始所有的内存管理都需要开发者手动管理,包括销毁
  • 以上是raw pointer常见API
  • 定义指针限定符let只限制当前指针不允许更换指向,并不能限制其指向内存的修改。

2. 指定类型指针(type pointer)

实例一:
代码语言:javascript
复制
var age : Int = 18
//使用值类型创建type pointer
let typePtr = withUnsafePointer(to: &age){$0}
//获取当前指针的值
print(typePtr.pointee)

输出结果

  • type pointer最简单的使用
  • lldb中可以使用该方式获取值类型的指针地址,在最开始已经有展示了。
  • 当前指针是不允许修改
实例二:
代码语言:javascript
复制
var age : Int = 18
//创建、获取可变类型指针
let typeMutablePtr = withUnsafeMutablePointer(to: &age) {ptr -> UnsafeMutablePointer<Int> in
    //可变指针的运算
    ptr.pointee += 10
    return ptr
}
print(age)
print(typeMutablePtr.pointee)

输出结果

  • 通过修改变量指针指向的值,来修改变量的值
实例三
代码语言:javascript
复制
//初始化
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 4)
for i in 0...3{
//    指针移动,
    let tempPtr = ptr.advanced(by: i)
//    赋值
    tempPtr.initialize(to: i)
}
for i in 0...3{
//    指针移动
    let tempPtr = ptr.advanced(by: i)
//    获取指针的值
    print("方式一:\(tempPtr.pointee)")

    print("方式二:\(ptr[i])")
    print("方式三:\((ptr+i).pointee)")
}
//下面两个成对出现,内存销毁
ptr.deinitialize(count: 4)
ptr.deallocate()

输出结果

  • type pointer相比raw pointer都需要advanced指针移动,但是不同的是位移的参数定义不一样,type pointer由于给定了类型只需要给定移动的步数不需要给定步长.
  • initializedeinitialize是成对的

3. 应用

应用一:实例对象绑定其他类型指针
代码语言:javascript
复制
struct Hr_HeapObject {
    var kind : UnsafeRawPointer
    var strongRef: UInt32
    var unownedRef: UInt32
    var age: Int
}
class clsModel {
    var age:Int = 18
}
var cls = clsModel()
HeapObject

cls的内存布局绑定到Hr_HeapObject中.

代码语言:javascript
复制
/**
 Unmanaged<T> : 任意类型的托管类;是对CoreFoundation类型 T的封装,相当于OC是__bridge
 passRetained: 转换后需要持有,增加引用计数
 passUnretained: 转换后不持有,不增加引用计数
 toOpaque:将托管类转为指针(不安全)
 */
let heapPtr = Unmanaged.passUnretained(cls as AnyObject).toOpaque()
/**
 bindMemory: 将 UnsafeMutableRawPointer 指针类型更改为 UnsafeMutablePointer<Hr_HeapObject>
 assumingMemoryBound: 若当前指针已经在内存中进行过类型绑定,则使用assumingMemoryBound做假定内存绑定。
                目的是告诉编译器不需要检查memory绑定
 */
let metaPtr = heapPtr.bindMemory(to: Hr_HeapObject.self, capacity: 1)
//输出
print("kind:\(metaPtr.pointee.kind)")
print("strongRef:\(metaPtr.pointee.strongRef)")
print("unownedRef:\(metaPtr.pointee.unownedRef)")
print("age:\(metaPtr.pointee.age)")

输出结果

  • HeapObject就是swift的类的结构体。在swift底层探索 01 - 类初始化&类结构一文中通过源码来推测了HeapObject以及HeapMetadata的结构,在本文中做了验证.
  • Unmanaged托管类
  • 这部分使用了passUnretained不对指针进行持有,所以不需要进行内存的管理。
  • bindMemory: 将 UnsafeMutableRawPointer 指针类型更改为 UnsafeMutablePointer<Hr_HeapObject>
  • assumingMemoryBound: 若当前指针已经在内存中进行过类型绑定,则使用assumingMemoryBound做假定内存绑定;目的是告诉编译器不需要检查memory绑定
HeapMetaData
代码语言:javascript
复制
//按照上文的逻辑和OC的逻辑,kind指针指向的是类的`元类`
struct hr_HeapMetaData {
    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的执行进行重新绑定
let clsPtr = metaPtr.pointee.kind.bindMemory(to: hr_HeapMetaData.self, capacity: 1)
print(clsPtr.pointee)

输出结果

应用二:获取结构体属性的指针
代码语言:javascript
复制
struct TestStruct {
    var age:Int = 18
    var phone:Int = 1888888888
}
//初始化
var testStr = TestStruct()
//type pointer转换
withUnsafePointer(to: &testStr) { (ptr) in
    //内部ptr是个read-only所以无法继续进行 type pointer转换
    
    /**
     MemoryLayout<TestStruct>.offset 获取对象指定变量的内存偏移值
     */
    let age = UnsafeRawPointer(ptr) + MemoryLayout<TestStruct>.offset(of: \TestStruct.age)!
    //age在内存中已经标记为Int了,所以使用assumingMemoryBound
    testPointeFunc(age.assumingMemoryBound(to: Int.self))
    
    let phone = UnsafeRawPointer(ptr) + MemoryLayout<TestStruct>.offset(of: \TestStruct.phone)!
    testPointeFunc(phone.assumingMemoryBound(to: Int.self))
}

func testPointeFunc(_ p:UnsafePointer<Int>) {
    print(p.pointee)
}

输出结果

应用三: 变量的指针类型转化
代码语言:javascript
复制
var tempAge = 18
func tempAgeFunc(_ p: UnsafePointer<Int64>) {
    print(p.pointee)
}
//直接调用类型不同会报错
//tempAgeFunc(tempAge)

// 获取指针地址
withUnsafePointer(to: &tempAge) { (ptr) in
// 1. 将当前指针的类型进行转换
// 2. 对未知类型指针进行类型绑定
    let temp = UnsafeRawPointer(ptr).bindMemory(to: Int64.self, capacity: 1)
    tempAgeFunc(temp)
}

方法二:

代码语言:javascript
复制
let tempPtr = withUnsafePointer(to: &tempAge) {$0 }
//对withUnsafePointer中的值临时进行修改,只在该作用域中有效,更加常用
tempPtr.withMemoryRebound(to: Int64.self, capacity: 1) { (ptr) in
    tempAgeFunc(ptr)
}
  • 指针类型可以随意转换

unsafeBitCast

unsafeBitCast 是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在 Swift 的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么。比如:

代码语言:javascript
复制
let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), to: CFString.self)
str // “meow”

【总结】

  1. 指针的内存是需要开发者手动管理的,有init/alloc一定会有dealloc
  2. 指针的优势是灵活,可以在一个首地址后添加任意类型的变量
  3. bindMemory: 更改内存绑定的类型,如果之前没有绑定那么就是首次绑定如果绑定过了,就是重新绑定类型。将指针的类型进行强制转换
  4. assumingMemoryBound: 假定内存绑定,目的是告诉编译器不需要检查memory绑定,达到混淆的目的;
  5. withMemoryRebound: 与bindMemory类似都是对指针进行类型绑定,不同的是withMemoryRebound只在当前作用域有效;
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/12/23 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 指针
    • Swift指针与OC指针类比
    • 1. 未指定类型指针(raw pointer)
      • 实例:
      • 2. 指定类型指针(type pointer)
        • 实例一:
          • 实例二:
            • 实例三
            • 3. 应用
              • 应用一:实例对象绑定其他类型指针
                • HeapObject
                • HeapMetaData
              • 应用二:获取结构体属性的指针
                • 应用三: 变量的指针类型转化
                • unsafeBitCast
                • 【总结】
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档