IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Swift —— 指针 -> 正文阅读

[移动开发]Swift —— 指针

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 = 18
    var 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 in
    print(ptr)
}

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

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

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

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

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

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

这个时候可以看到age的值也变了。
在这里插入图片描述
一种是直接内存分配:

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

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

struct LGTeacher {
    var age: Int
    var 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: Int
    var 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 = 18
    var name: String = "hello"
}

var size: UInt = 0

var ptr = getsectdata("__TEXT", "__swift5_types", &size)

print(ptr)

运行后发现这里地址和macho里面的地址是一样的。
在这里插入图片描述
在这里插入图片描述
接下来需要用到虚拟内存地址等,所以需要去获取。这里先找到machoHeader的内存地址。然后需要拿到虚拟内存地址,其在__LINKEDIT里面,需要用到里面的VM Address File Offset,所以通过getsegbyname拿到__LINKEDIT,看到这里返回segment_command_64,看到segment_command_64的结构,可以看到vmaddrfileoff,使用vmaddr 减去fileoff才是加载的基地址。之前的ptr是加过基地址的,所以ptr需要减去linkBaseAddress。

class LGTeacher {
    var age: Int = 18
    var name: String = "hello"
}

var size: UInt = 0

var 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 - linkBaseAddress
    print(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)>) in
    testPoint(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>) in
    testPoint(ptr)
}

7. 内存管理

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

class LGTeacher {
    var age: Int = 18
    var 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 = 18
    var 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 = 18
    var name: String = "ls"
}

var s:LGTeacher? = LGTeacher()
print( Unmanaged.passUnretained((s as AnyObject)).toOpaque())
s = nil

print("end")

运行后可以看到这里变成了0x0000000100000003,也就是IsDeinitingShift的位置变为了1.
在这里插入图片描述

在这里插入图片描述
那么强引用是怎么去添加的呢?这里看到是在swift_retain方法里面调用了increment方法,
在这里插入图片描述
increment方法里面则是调用了incrementStrongExtraRefCount。
在这里插入图片描述
在incrementStrongExtraRefCount就是1左移33位。
在这里插入图片描述

8.循环引用

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

class LGTeacher{
    var age: Int = 18
    var name: String = "Kody"
    var subject: LGSubject?
}
class LGSubject{
    var subjectName: String
    var subjectTeacher: LGTeacher
    init(_ subjectName: String, _ subjectTeacher: LGTeacher) {
        self.subjectName = subjectName
        self.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 = 18
    var name: String = "Kody"
}
weak var t = LGTeacher()
print( Unmanaged.passUnretained((t as AnyObject)).toOpaque())
print("end")

运行后发现这里对比正常对象多调用了swift_weakInit.
在这里插入图片描述
看到swift_weakInit在底层调用的是nativeInit.
swift_weakIn
而弱引用就是形成一个散列表。
在这里插入图片描述
所以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 = 18
    var name: String = "Kody"
    var closure: (() -> ())?
    deinit {
        print("deinit")
    }
}

func testClosure() {
    let t = LGTeacher()

    t.closure = { [weak t] in
        if let strongSelf = t {
            print( strongSelf.age)
        }
    }

}

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

class LGTeacher{
    var age: Int = 18
    var name: String = "Kody"
    var closure: (() -> ())?
    deinit {
        print("deinit")
    }
}

func testClosure() {
    let t = LGTeacher()

    t.closure = { [weak t] in
        withExtendedLifetime(t) {
            print(t!.age)
        }
    }

}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章           查看所有文章
加:2022-06-29 19:12:58  更:2022-06-29 19:16:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 3:49:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码