内存管理
-
iOS 管理对象 采用的是内存引用计数管理 当我们生产一个对象。
- (生成对象) 我们就对这个对象引用计数加一 ,当我们在对这个对象 去做某些事(持有对象) 相当于我们持有这个对象 这事这个对象的引用计数又会 加一 。当我们做完某些事 不需要这个对象的时候 (释放对象)我们会对这个对象引用计数减一。当我们完全不使用的时候 我们应该对这个对象 进行销毁(废弃对象)
- 生成并持有对象 (alloc、new、copy、mutableCopy等方法)
- 持有对象 (retain 方法)
- 释放对象(release 方法)
- 废弃对象(dealloc方法)
-
ARC 自动引用计数
- 自动引用计数 是Xcode 4.2或以上版本 LLVM 编译器 3.0 或以上版本引入新技术。
- 在编写代码时 无需再次键入 retain 或 release 代码 这在降低程序崩溃、内存泄露等风险的同时 很大程度上减少了开发程序的工作量。
-
GNU 和苹果源码 引用计数区别
- GNU
- alloc 类方法用 struct obj_layout中的retained 整数来保存引用计数,并写入对象内存头部(GNU 少量代码可以完成、能够统一管理引用计数用内存块与对象用内存块)
- 苹果
- 苹果实现大概使用了散列表来管理引用计数(对象内存块的分配无需考虑内存块头部、引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块)
- GNU
-
autorelease
- NSAutoreleasePool 对象的生存周期相当于C语言变量的作用域。对于所有autourelease 实例对象,在废弃NSAutorelease 对象时,都将调用release 方法。
- NSRunLoop 每次循环过程中NSAutoreleasePool 对象被生成或废弃(NSAutoreleasePool 生命周期 在与NSRunLoop开发-结束)
- GNU autorelease 源码实现
- autorelease 实例方法本质调用 NSAutoreleasePool 对象的 addObject 方法
- NSAutoreleasePool 内部保存对象池子是一个数组。 经常有人问 如果嵌套生成或持有 NSAutoreleasePool 对象 那个pool释放会有效果 连接列表 所有的对象池子都是一个,这个相同于往 MutableAry 对象追加数据一样
-
修饰符
- __strong==strong==copy 修饰符 ARC 下 通过 strong 修饰符 不必再次写入 retain 或 release 代码。通过 strong 修饰 自己生成的对象 自己持有 和 非自己生产的对象 自己也能持有。废弃带有strong 修饰的变量 或者对变量赋值 都可以做到不再需要自己持有的对象释放。
-
__weak==weak 修饰符 weak 修饰符可以避免循环引用 弱引用不能持有对象实例,在超出变量作用域时 对象就会释放 这样就可以解决循环引用问题。
- 用weak 修饰对象 若该对象被废弃,则此弱引用将自动失效且处于nil 被赋值的状态
- 在weak 修饰的变量 在访问引用对象 其实是访问注册到autoureleasePool的对象,这是因为在访问引用对象过程中,该对象有可能被废弃 把对象注册到autoureleasePool 里面 在autoureleasePool 结束之前都能保证该对象存在。
- 如果大量使用附有weak修饰的变量 注册到autoureleasePool的对象也会大量的增加 因此在使用附有weak修饰的变量 最好暂时用strong 修饰符之后再使用 。用strong 修饰之后只会在autoureleasePool 注册一次 大大减少了系统工作量
- 当用weak 修饰对象时 weak修饰的变量的地址注册到weak表里(weak表与引用计数表相同都是散列表) 当对象废弃时 则把变量的地址从weak表中删除。
- 对象废弃步骤
- 1 、从weak表中获取废弃对象的地址为键值的记录
- 2、将包含在记录中所有附有weak 修饰变量的地址 赋值为nil
- 3、从weak 表中删除该记录
- 4、从引用计数表中删除废弃对象的地址为键值的记录
- 如果大量的使用weak修饰符变量 则会消耗相应的CPU 资源。(只需要避免循环引用时使用)
__unsafe_unretained==assign 修饰符 作用类似weak 修饰符 但在使用 __unsafe_unretained 要确保被赋值的对象却是存在。 当对象废弃是 _unsafe_unretained修饰对象 不会置nil
Block
-
Block 简介
- Block 是带有自动变量的匿名函数。 匿名函数就是不带名称函数
- 表达式: ^ 返回值类型 参数列表 表达式
-
Block类型变量与C语言变量完全相同
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全部变量 -
Block 本质
- OC 中由类生成对象 意味着结构体这样 生成由改类生成的对象的结构体实例。生成的各个对象 即由改类生成的对象各个结构体实例 通过成本变量isa 保存该类的结构体实例指针。通过clang 可以看到 Block 指针赋值给Block的结构体成员变量isa 所以Block也是oc对象。
-
Block 变量截取
- 截获自动变量值 在执行Block 语法时 Block 语法表达式所使用的自动变量值被保存到Block的结构实例中(既Block 中),自动变量值被Block 截获 只能执行Block语法瞬间值。保存后就不能修改此值。
- Block 中使用自动变量后 在Block 的结构体实例中重写改自动变量也不会改变原先截获的自动变量
- 静态变量 、静态全局变量、全局变量 可以在Block 中修改
-Block截获 静态变量时 静态变量是将指针 转递给 Block结构体的构造函数保存,这也是超出作用域使用变量的最简单的方法
-
__block 说明符
- __block 是存储域说明符 用于指定将变量值设置到那个存储域中。
- 当我们用 _block修饰变量 _block也同Block 一样变成__Block_byref_val_0 结构体类型的自动变量,即栈上生成的_Block_byref_val_0结构体实例。这意味着该结构体持有相当于原自动变量的成员变量。
- __Block_byref_val_0 结构体实例的成员变量 _forwarding 持有指向该实例自身的指针。通过成员变量_forwarding访问成员变量val (val 就是截获的自动变量)
- 为什么_forwarding会指向该实例自身的指针呢?
- 配置全局Block 从变量作用域也可以通过指针安全地使用,但设置在栈上Block 如果所属的变量作用域结束 该block就被废弃。由于__block变量也配置在栈上,同样的 如果所属的变量作用域结束,则block变量也会被废弃。
- Blocks 提供了将Block 和 __block 变量从栈上赋值到堆上方法。将栈上Block 赋值到堆上 即使变量作用域结束 堆上的block 还可以继续存在。(栈上_forwarding指向复制到堆上_block变量结构体)
- __block 变量用结构体成员变量_forwarding可以实现无论_block 变量配置在栈上还是在堆上都能够正确的访问
-
Block存储类型
- 全局Block 在使用全局变量的地方不能使用自动变量 所以不存在对自动变量的截获
- 栈Block
- 堆Block
-
截获对象
- 当Block 截获oc 对象 会在_mian_blokc_desc_0 结构体中增加的成员变量copy和dispose函数 ,作为指针赋值给成员变量_main_block_copy_0函数 和_mian_blokc_dispose_0函数
- _main_block_copy_0 里面调用 block_object_assgin 相当于 retain 实例方法函数,将对象赋值在对象类型的结构成员变量中(调用时机 栈上block 复制到堆上)
- _mian_blokc_dispose_0 里面调用 block_object_dispose 函数 相当于release实例函数方法 释放赋值在对象类型的结构体成员变量中的对象(调用时机 堆上block 被废弃时)
- 什么时候栈上block 会被复制到堆上?
- 调用 block 的copy 实例方法
- block 作为函数的返回值返回时
- 将block 赋值给附有 strong 修饰符 id类型或block 成员变量时
- 当Block 截获oc 对象 会在_mian_blokc_desc_0 结构体中增加的成员变量copy和dispose函数 ,作为指针赋值给成员变量_main_block_copy_0函数 和_mian_blokc_dispose_0函数
-
Block 循环引用
- 可以通过weak 和 unsafe_unretained 来修饰 但要注意unsafe_unretained 修饰变量销毁值 不置nil 情况
- 也可以使用__block 来修饰 配合。 但要注意block 必须执行block 要不然就会出现循环引用问题
GCD
-
Dispatch Queue
- 执行处理的等待队列 。通过dispatch_async 函数等API,在block 语法中编写想要执行的处理并将其追加到 Dispatch Queue 中。 Dispatch Queue 按照追加的顺序FIFO 执行处理。
- Serial Dispatch Queue (串行) 等待现在执行中任务处理结束
- Concurrent Dispatch Queue(并行)不等待现在执行中任务处理结束
-
GCD API
-
dispatch_after
- dispatch_after 函数并不是在指定时间后执行处理,而是指定时间追加处理到Dispatch Queue。
- 因为Mian Dispatch Queue 在主线程的RunLoop 中执行,所以在比如每隔1/60 秒执行RunLoop 中 Block 最快在3秒后执行,最慢在3秒+1/60 秒执行,并且 在Mian Dispatch Queue 有大量任务追加或主线程的处理本身有延迟的。
-
Dispatch Group
- 当我们处理多个并行队列时想在多个任务处理结束时 执行其他操作 。我们这是应该就需要用 Dispatch Group,使用Dispatch Group 可以监视任务结束状态,一旦监视到任务结束,就可将结束的处理追加到Dispatch Queue 中。
- dispatch_group_notify 函数会将执行Block 追加到Dispatch Queue 中 ,第一个参数指定要监视的Dispatch Group 第三个参数Block 追加到第二参数Dispatch Queue中。
-
dispatch_barrier_async
- dispatch_barrier_async 函数会等待追加到并行队列并行任务处理全部结束之后 在将指定处理追加到该并行队列中。 然后在由dispatch_barrier_async 函数追加处理执行完毕后 并行队列在恢复之前操作 执行追加到该队列的任务。
-
dispatch_sync&&dispatch_async
- dispatch_async 函数 async 意味 非同步 就是将指定block 非同步地追加到指定 Dispatch Queue 中,dispatc_async 函数不做任何等待。
- dispatch_sync 函数 sync 意味 同步 就是将指定block 同步地追加到指定 Dispatch Queue 中,在追加block结束之前 dispatch_sync 函数会一直等待
-
dispatch_apply
- dispatch_apply 函数是dispatch_sync 函数 和Dispatch Group 的管理API。该函数按指定的次数将指定block 追加到指定Dispatch Queue 中 并等待全部出了执行结束。
-
Dispatch Semaphore
- Dispatch Semaphore 是持有计数的信号,该计数底是多线程编程中的计数类型信号,计数为0时等待 计数为1或大于1时,减去1而不等待
- dispatch_semaphore_create(X) X为计数初始值 保存可访问对象线程个数
- dispatch_semaphore_wait 是将 Dispatch Semaphore 的计数 值减一 (由于 Dispatch Semaphore 的计数值大于等于1 所以 Dispatch Semaphore 的计数 值减一)
- dispatch_semaphore_signal 是将 Dispatch Semaphore 的计数 值加一 (排他控制处理结束 将 Dispatch Semaphore 的计数 值加一 恢复之前状态)
-