Swift —— 指针

  • 1.指针
  • 2. 指针类型
  • 3. 原始指针的使用
  • 4. 泛型指针的使用
  • 5. 指针读取macho中的属性名称
  • 6. 内存绑定
  • 7. 内存管理
  • 8.循环引用
    • 8.1 弱引用
    • 8.2 无主引用
  • 9. 闭包循环引用

1.指针

为什么说指针是不安全的呢?主要以下几点:

  • 比如我们在创建一个对象的时候,是需要在堆区分配内存空间的,但是这个内存空间的生命周期是有限的,也就意味着如果我们使用指针指向这块内存空间,如果当前内存空间的生命周期到了(也就是引用计数为0了),那么当前的指针就成了未定义的行为,也就是野指针。
  • 我们创建的内存空间是有边界的,比如创建一个大小为10的数组,这个时候通过指针访问到index = 11 的位置,这个时候就越界了,访问了一个未知的内存空间。
  • 指针类型和内存空间的值类型不一致,也是不安全的。

2. 指针类型

swift中的指针分为2类,typed pointer (指定数据类型指针)raw pointer(未指定数据类型指针,也叫原生指针),基本上我们接触的指针类型有以下几种

  • unsafePointer,相当于oc中的const T *,指针以及指向内容都不可变
  • unsafeMutablePointer,相当于oc中的 T *,指针以及指向内容都可变
  • unsafeRawPointer,相当于oc中的const Void *,指针指向的内存区域未定
  • unsafeMutableRawPointer,相当于oc中的const Void *,指针指向的内存区域未定
  • unsafeBufferPointer,连续的内存空间
  • unsafeMutableBufferPointer
  • unsafeRawBufferPointer
  • unsafeMutableRawBufferPointer

3. 原始指针的使用

接下来使用raw pointer 储存4个整形的数据,这里使用unsafeMutablePointer.

let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)for i in  0..<4 {p.storeBytes(of: i, as: Int.self)
}for i in  0..<4 {let value = p.load(fromByteOffset: i * 8, as: Int.self)print("index\(i) value:\(value)")
}

但是这里运行后发现取出来的值和想要的不一样,这是因为存的时候指针没有位移相应的位置,也就是步长。

在IOS里面有测量当前大小的工具,分别是

  • MemoryLayout.size
  • MemoryLayout.stride
  • MemoryLayout.alignment

例如下面代码

struct LGTeacher {var age: Int = 18
}print(MemoryLayout<LGTeacher>.size)
print(MemoryLayout<LGTeacher>.stride)
print(MemoryLayout<LGTeacher>.alignment)

运行后发现都是8


而在结构体添加一个bool属性

struct LGTeacher {var age: Int = 18var sex: Bool = true
}

然后重新运行,发现这里打印的值就不一样了。这里可以知道,size是struct结构体的大小,stride是占用内存的实际大小,alignment是对其的大小。

所以这里知道,我们储存的时候先要移动 i * 步长信息的位置,然后在储存值。

for i in  0..<4 {p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i , as: Int.self)
}

这样运行后就可以看到打印的是期望的值了

当然,在使用完指针之后,需要调用deallocate来释放内存空间。

p.deallocate()

4. 泛型指针的使用

泛型指针,也叫类型指针,指定当前指针已经绑定到了具体的类型。

获取withUnsafePointer的方式有两种,一种是通过已有变量获取:

如果闭包是最后一个参数的话,可以写成尾随闭包。

var age = 18
withUnsafePointer(to: &age){ ptr inprint(ptr)
}

指针的具体内容使用pointee访问,pointee代表指针指向的具体数据类型,所以下列代码是可行的。

age = withUnsafePointer(to: &age){ ptr inptr.pointee + 21
}

但是需要注意的是,这里的pointee是只读属性,所以下面的代码是不可行的。

age = withUnsafePointer(to: &age){ ptr inptr.pointee +=  21
}

但是如果使用的是withUnsafeMutablePointer的话,那么pointee就可变了。

withUnsafeMutablePointer(to: &age) { ptr inptr.pointee +=  21
}

这个时候可以看到age的值也变了。

一种是直接内存分配:

var age = 10
let tPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
tPtr.initialize(to: age)

这里也有两种方式来初始指针内存,一种是使用:

struct LGTeacher {var age: Intvar height: Double
}
var tPtr = UnsafeMutablePointer<LGTeacher>.allocate(capacity: 5)tPtr[0] = LGTeacher(age: 18, height: 180.0)
tPtr[1] = LGTeacher(age: 18, height: 180.0)
tPtr.deinitialize(count: 5)
tPtr.deallocate()

一种是使用:

struct LGTeacher {var age: Intvar height: Double
}
var tPtr = UnsafeMutablePointer<LGTeacher>.allocate(capacity: 5)tPtr.initialize(to: LGTeacher(age: 18, height: 180))
tPtr.advanced(by: MemoryLayout<LGTeacher>.stride).initialize(to: LGTeacher(age: 18, height: 180))tPtr.deinitialize(count: 2)
tPtr.deallocate()

这里使用完指针后,需要调用deinitialize和deallocate。deinitialize把内存空间全部抹成0,也就是数据清零。deallocate回收内存空间。在实际开发中可以用defer来执行deinitialize和deallocate。

5. 指针读取macho中的属性名称

之前在macho通过地址的操作找到了属性名称,现在通过指针来读取macho中的属性名称。这里先获取到types里面的地址。

class LGTeacher {var age: Int = 18var name: String = "hello"
}var size: UInt = 0var ptr = getsectdata("__TEXT", "__swift5_types", &size)print(ptr)

运行后发现这里地址和macho里面的地址是一样的。


接下来需要用到虚拟内存地址等,所以需要去获取。这里先找到machoHeader的内存地址。然后需要拿到虚拟内存地址,其在__LINKEDIT里面,需要用到里面的VM AddressFile Offset,所以通过getsegbyname拿到__LINKEDIT,看到这里返回segment_command_64,看到segment_command_64的结构,可以看到vmaddrfileoff,使用vmaddr 减去fileoff才是加载的基地址。之前的ptr是加过基地址的,所以ptr需要减去linkBaseAddress。

class LGTeacher {var age: Int = 18var name: String = "hello"
}var size: UInt = 0var ptr = getsectdata("__TEXT", "__swift5_types", &size)var mhHeaderPtr = _dyld_get_image_header(0)
var secCommondPtr = getsegbyname("__LINKEDIT")
var linkBaseAddress: uint64 = 0
if let vmaddress = secCommondPtr?.pointee.vmaddr, let fileoff = secCommondPtr?.pointee.fileoff {linkBaseAddress = vmaddress - fileoff
}var offset: uint64 = 0
if let unwrappedPtr = ptr {let intRepresentation = uint64(bitPattern: Int64(Int(bitPattern: unwrappedPtr)))offset = intRepresentation - linkBaseAddressprint(offset)
}


运行后这里得到16164,转换为16进制就得到0X3F24。

那么程序编译之后就得到了swift5_types里面的地址为0X3F24。


接下来要拿到0X3F24地址里面的内容。这里先拿到程序运行的首地址,那么加上offset就得到了0X3F24地址里面的内容存放在内存当中的真实地址。然后将地址转换为指针类型,使用pointee获得里面的内容。

6. 内存绑定

Swift 提供了3种不同的API来绑定/重新绑定指针:

  • assumingMemoryBound(to:) : 有些时候我们处理代码的过程中,只有原始指针(没有保留指针类型),但此刻对于处理代码的我们来说明确知道指针的类型,这个时候就可以使用assumingMemoryBound来告诉编译器预期的类型(注意:这里只是让编译器绕过类型检查,并没有发生实际类型的转换)。有时候,我们的指针类型是相似的,而我们不想通过一系列操作来转换类型,这个时候就可以使用assumingMemoryBound告诉编译器预期的类型来绕过类型检查。
    例如下面的代码是无法运行的,但是他们的指针类型是类似的,本质上是一样的。
func testPoint(_ p: UnsafePointer<Int>) {print(p)
}let tuple = (10,20)
withUnsafePointer(to: tuple) { (tuplePtr:UnsafePointer<(Int,Int)>) intestPoint(tuplePtr)
}

那么如果这里使用assumingMemoryBound来告诉编译器预期的类型是Int,那么这里就可以运行了。

  • bind Memory(to:capacity:) : 用于更改内存绑定的类型,如果当前内存还没有内存绑定,则将首次绑定为该类型,否则就重新绑定为该类型,并且内存中的所有的值都会变成改类型。
    不同与assumingMemoryBound的是,这里发生了实际类型的转换。

  • withMemoryRebound(o:capacity:body:) :当我们给外部函数传递参数的时候,不免会有一些数据类型的差距。如果我们进行类型转换,必然要来回复制数据。这个时候可以使用withMemoryRebound来临时更改内存绑定类型。
    下面就将uint8临时更改内存绑定类型为Int8类型了,减少了代码的复杂度。

func testPoint(_ p: UnsafePointer<Int8>) {print(p)
}let UInt8Ptr = UnsafePointer<uint8>.init(bitPattern: 10)UInt8Ptr?.withMemoryRebound(to: Int8.self, capacity: 1) { (ptr: UnsafePointer<Int8>) intestPoint(ptr)
}

7. 内存管理

Swift 中使用自动引用计数(ARC)机制来追踪和管理内存。

class LGTeacher {var age: Int = 18var name: String = "ls"
}var s = LGTeacher()
print(Unmanaged.passUnretained(s as AnyObject).toOpaque())print("end")

运行后发现是3.

到源码中查看refCount,看到是InlineRefCounts类型。

然后找到InlineRefCounts,发现是模版类,接受一个泛型参数。

RefCounts里面的API都是操作RefCountBits这个泛型参数,所以RefCounts是对引用计数的一个包装,而引用计数类型是传进来的参数。

之后找到InlineRefCountBits,这里看到真实操作的类是RefCountBitsT

要知道引用计数是什么,就要看RefCountBitsT里面的属性,这里就看到引用计数bits,他的类型BitsType是由RefCountBitsInttype属性定义的。

查找RefCountBitsInt可以发现type是一个uint64_t的位域信息。


那么创建一个实例的时候,引用计数是多少呢?看到_swift_allocObject_,这里使用HeapObject创建实例。

然后看到这里面对refCounts进行了赋值InlineRefCounts::Initialized。

然后进来发现是枚举类型

往下看枚举类型传进去的值是0,1,而RefCountBits就是RefCountBitsT类型。

接下来找RefCountBitsT的初始化函数,那么就看到这里是strongExtraCount是0,unownedCount是1.

那么之前的0x0000000000000003代表的就是PureSwiftDeallocShift为1,UnownedRefCountShift为1了。

这里在代码中添加两个强引用。

class LGTeacher {var age: Int = 18var name: String = "ls"
}var s = LGTeacher()
var s1 = s
var s2 = s
print(Unmanaged.passUnretained(s as AnyObject).toOpaque())print("end")

运行后打印看到这里就是4了,这是因为2存储在了高33位。

64位位置信息:

验证一下,将s变为可选参数然后后面置位nil,

class LGTeacher {var age: Int = 18var name: String = "ls"
}var s:LGTeacher? = LGTeacher()
print( Unmanaged.passUnretained((s as AnyObject)).toOpaque())
s = nilprint("end")

运行后可以看到这里变成了0x0000000100000003,也就是IsDeinitingShift的位置变为了1.


那么强引用是怎么去添加的呢?这里看到是在swift_retain方法里面调用了increment方法,

increment方法里面则是调用了incrementStrongExtraRefCount。

在incrementStrongExtraRefCount就是1左移33位。

8.循环引用

刚才说到了强引用,那么使用强引用就会有一个问题,就是循环引用。
下面就是一个案例,这里t持有subject,subject也持有t,这样就造成了循环引用,导致无法释放。

class LGTeacher{var age: Int = 18var name: String = "Kody"var subject: LGSubject?
}
class LGSubject{var subjectName: Stringvar subjectTeacher: LGTeacherinit(_ subjectName: String, _ subjectTeacher: LGTeacher) {self.subjectName = subjectNameself.subjectTeacher = subjectTeacher
} }
var t = LGTeacher()
var subject = LGSubject.init("Swift ", t)
t.subject = subject

Swift提供了两种方法来解决在使用类的属性时所遇到的循环引用问题:弱引用和无主引用

8.1 弱引用

弱引用不会对其引用的实例保持强引用,因而不会阻止ARC释放被引用的实例,这个特性阻止了引用变为循环引用。声明属性或者变量时,在前面加上weak关键字声明这是一个弱引用。

由于弱引用不会强保持对实例的引用,所以说实例被释放了弱引用依旧引用着这个实例也是有可能的。因此,ARC会在被引用的实例被释放时自动的设置弱引用为nil,由于弱引用需要允许他们的值为nil,所以他们一定的是可选类型。

那么用弱引用的对象的引用计数会有什么变化呢?输入下面代码

class LGTeacher{var age: Int = 18var name: String = "Kody"
}
weak var t = LGTeacher()
print( Unmanaged.passUnretained((t as AnyObject)).toOpaque())
print("end")

运行后发现这里对比正常对象多调用了swift_weakInit.

看到swift_weakInit在底层调用的是nativeInit.

而弱引用就是形成一个散列表。

所以formWeakReference本质上就是创建一个散列表。

而在allocateSideTable里面就会判断是否有散列表,有的话就得到散列表并返回,如果在析构就返回nullptr。

如果没有的话,那么往下就会创建散列表,看到这里的类型是HeapObjectSideTableEntry。

找到HeapObjectSideTableEntry,这里发现,swift里面有两种引用计数:InlineRefCountBits和SideTableRefCountBits。在HeapObject里面如果没有弱引用就是存的InlineRefCountBits,如果用弱引用存的就是HeapObjectSideTableEntry这个实例对象,HeapObjectSideTableEntry里面就有弱引用的信息。

找到SideTableRefCounts。

找到SideTableRefCountBits,发现这里也是继承自RefCountBitsT,不过这里多了一个weakBits,

添加弱引用,查看引用计数的变化。

var t = LGTeacher()print( Unmanaged.passUnretained((t as AnyObject)).toOpaque())
weak var t1 = t
print("end")

运行后看到变成了0xc0000000200e6308,这里62和63位都变成了1.


在看到allocateSideTable里面生成sidetable的方法InlineRefCountBits。看到这里把散列表位置像右移了3位,然后把62和63位都变为1.

那么把之前的地址0xc0000000200e6308左移三位,得到0x100731840,看到这里0x100731840前八个字节存放的HeapObject的地址,然后0x0000000000000003 和 0x0000000000000002存的是strongCount和weakCount。

8.2 无主引用

无主引用也不会对其引用的实例保持强引用,但是无主引用不是一个可选类型,它假定是永远有值的,所以无主引用相对于弱引用来说不够那么安全。如果强引用的双方,生命周期没有关联,使用weak,比如delegate。如果其中一个对象,另外一个对象也要跟着销毁,就要使用unowned。weak比unowned来说更安全,而unowned则性能更好,一般来说,使用weak就可以了。

9. 闭包循环引用

swift中,闭包会默认捕获外部的变量。这里可以看出来,闭包内部对外部变量的修改会改变外部原始变量的值。

而这里看到,LGTeacher的deinit没有被调用,这是因为t是全局变量。

这个时候,deinit就被调用了。

而如果让LGTeacher持有这个闭包,那么deinit就不会被调用了,因为闭包和对象形成了循环引用。

那么如何解决这个问题呢?这里在捕获列表使用weak修饰t就可以了。

当然,这里也可以使用unowned

当声明了一个捕获列表,编译器会在当前程序运行的上下文中找到与之同名的变量或者常量,然后进行初始化,这里也就是把0赋值给了闭包中的age,而闭包中的age和外面的age是不同的东西,所以外面的age在变化,闭包中的值也不会变化。这里虽然捕获值是在调用closure的时候,但是因为使用了捕获列表,所以对于age的变量,不再捕获age = 10,而是在定义的上下文中用age的值来初始化捕获列表中age的值。

捕获列表中的age是一个常量,所以这里也无法改变age的值。

对于值类型来说,是调用的时候在捕获值,所以这里打印11,13,14.

OC中有强弱共舞,那么Swift中有没有呢? 下面就是Swift中的强弱共舞,这里将t可选值做了模式匹配,将值做了解包的操作,然后赋值给了strongSelf。

class LGTeacher{var age: Int = 18var name: String = "Kody"var closure: (() -> ())?deinit {print("deinit")}
}func testClosure() {let t = LGTeacher()t.closure = { [weak t] inif let strongSelf = t {print( strongSelf.age)}}}

这里还有另一个写法,就是使用withExtendedLifetime。

class LGTeacher{var age: Int = 18var name: String = "Kody"var closure: (() -> ())?deinit {print("deinit")}
}func testClosure() {let t = LGTeacher()t.closure = { [weak t] inwithExtendedLifetime(t) {print(t!.age)}}}

Swift —— 指针相关推荐

  1. Swift 对象内存模型探究(一)

    MemoryLayout 基本使用方法 HandyJSON 是 Swift 处理 JSON 数据的开源库之一,类似 JOSNModel,它可以直接将 JSON 数据转化为类实例在代码中使用. 由于 S ...

  2. HandyJSON:Swift语言JSON转Model工具库

    背景 JSON是移动端开发常用的应用层数据交换协议.最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上. 但在编程的时候,处 ...

  3. iOS-学习资源看我就够了

    iOS学习视频及资料 懒人demo大全更新至4.0版 (oc + swift) 史上最全iOS 开发工具集合 笔试面试 iOS-笔试面试总结 网络通信 <iOS 对 HTTPS 证书链的验证&g ...

  4. iOS开发的跳槽之路的心路历程分享(iOS进阶路线路)

    序言 我相信很多人都在说,iOS行业不好了,iOS现在行情越来越难了,失业的人比找工作的人还要多.这是事实,物竞天择,优胜劣汰,我们一样适用于丛林法则.所以我们要不断学习,提升自己的技能,不仅仅专业方 ...

  5. MIT 6.s081学习笔记

    MIT 6.s081学习笔记 introduction 计算机组织结构: 最底部是一些硬件资源,包括了CPU,内存,磁盘,网卡 最上层会运行各种应用程序,比如vim,shell等,这些就是正在运行的所 ...

  6. 指针 与 swift 中的引用

    指针 如果你有 C,C++ 或者 Objective-C 语言的经验,那么你也许会知道这些语言使用指针来引用内存中的地址.一个 Swift 常量或者变量引用一个引用类型的实例与C语言中的指针类似,不同 ...

  7. Swift之深入解析“指针”的使用和实现

    一.指针 ① 指针类型 Swift 中的指针分为两类: typed pointer 指定数据类型指针,即 UnsafePointer,其中 T 表示泛型: raw pointer 未指定数据类型的指针 ...

  8. Swift 中的指针使用

    SWIFT 中  指针被映射为泛型 UnsafePointer<T> UnsafeMutablePointer<T> 表示一组连续数据指针的 UnsafeBufferPoint ...

  9. swift学习笔记-----swift中的指针

    swift语言为了简化,把指针隐形化了.没有像OC中那样的" * ".把底层的问题交给C语言去处理,我们可以在swift中调用C 语言来解决.当然,OC也是可以调用的. 但是在某些 ...

最新文章

  1. PHP CI框架目录结构及运行机制
  2. 赴美工作常识(Part 4 - 面试)
  3. 用stream将dgv中的数据导出到word或excel中小例子
  4. 今天小暑是什么时间_小暑适合发朋友圈的说说 小暑吐槽天气热的搞笑幽默说说...
  5. idea中如何将一个普通项目转换为maven项目
  6. C++(STL):35---multimap容器
  7. c语言-基本计算 pm2.5,C语言程序设计题(A卷).doc
  8. 【转】【开源专访】谢宝友:会说话的Linux内核
  9. linux下完全删除mysql
  10. 机器学习与深度学习视频讲解
  11. 判断两个矩形是否相交
  12. php 图形库 锯齿,PHP imageantialias - 是否使用抗锯齿(antialias)功能
  13. python语音库_python音频库dejavu原理浅析
  14. 友盟推送 php,laravel5.4+umeng(友盟)集成app消息推送功能
  15. Flutter性能优化实践 —— UI篇,头条android面试节奏
  16. 用python的statamodels模块拟合VAR模型
  17. Raptor-选组长
  18. 禁忌搜索算法解决TSP问题
  19. 2.C++秒懂高精度数
  20. 深入解析 Raft 模块在 KaiwuDB 中的优化改造(下)

热门文章

  1. class_addMethod的详解
  2. 面试官常问的 web前端 问题(四)
  3. 【软件项目管理】项目建议书是项目立项阶段开发的文档
  4. RVDS 2.2破解全教程(含图)
  5. Yii2 framework学习笔记(五) -- 为后台更换皮肤
  6. 爬虫学习,尝试爬取小说网站
  7. 微信小程序开发.小程序入门(上)
  8. 网络推广都有那些平台
  9. 三分钟读懂外汇保证金交易,告诉你我为什么要做外汇交易!(开篇)
  10. etiger.vip 293.野兽出没