iOS备忘录iOS-内存管理iOS知识点面试

《Objective-C 高级编程 iOS 与 OS X 多线程

2019-10-14  本文已影响0人  赫子丰

一、ARC

1. autorelease

使用 NSMutableArray 类的 array 方法等可以取得谁都不持有的对象,这些方法都是通过 autorelease 而实现的。

2. GNUstep

GNUstep 是 Cocoa 框架的互换框架,可看到源码,参考 ARC 的实现方式。
简单来说就是通过一个 obj_layout 的结构体中的 retained 来保存了引用计数:

  1. 在 Objective-C 的对象中存有引用计数这一整数值;
  2. 调用 alloc 或者是 retain 方法后,引用计数加一;
  3. 调用 release 之后,引用计数减一;
  4. 引用计数数值为0时,调用 dealloc 方法废弃对象。

3. 引用计数两种保存方式对比

GNUstep将引用计数保存在对象占用内存块头部的变量中,而苹果的实现则是保存在引用计数表的记录中,两者各有优点。
通过内存块头部管理的好处是:

通过引用计数表来管理的好处是:

4. NSRunLoop 每次循环过程中 NSAutoreleasePool 对象被生成或者是废弃。

5. ARC 所有权修饰符有 4 种:

__strong(默认)
__weak
__unsafe_unretained
__autoreleasing

6. 所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。

7. ARC 有效情况下的编码规则:

8. 属性声明的关键字与所有权修饰符的对应关系:

9. ARC 的实现

ARC 是由编译器进行内存管理的,但实际上只有编译器是无法完全胜任的,在此基础上还需要 Objective-C 运行时库的协助:

10. 关于 __weak 修饰符的实现

若附有__weak修饰符的变量所引用的对象被抛弃,则将nil赋值给该变量。
若用附有__weak修饰符的变量,即是试用注册到autoreleasepool中的对象。

将 __strong 修饰的 obj 赋值给 __weak 的 obj1 将会发生什么呢?
eg:id __weak obj1 = obj;

下面👇是编译器的模拟代码:
id obj1;
objc_initWeak(&obj1, obj);//初始化 obj1
objc_destroyWeak(&obj1);//释放 obj1

1.其中 objc_initWeak 函数又会调用 objc_storeWeak 函数
objc_initWeak(&obj1, obj)等同于:
    obj1 = 0;
    objc_storeWeak(&obj1, obj);
    
2.objc_destroyWeak 函数会将 0 作为参数调用 objc_storeWeak 函数 
objc_destroyWeak(&obj1)等同于:
       objc_storeWeak(&obj1, 0);

完整的模拟代码如下:

id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);   

//这里第一个storeWeak函数把第二个参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中。
//可以注意到,第二次调用的时候第二个参数为0, 也就是说第二个参数为-0的时候,会把变量的地址从weak表中删除

weak表是什么呢? 此时联想到引用计数表,他们都是利用散列来实现的。 这里的一个键值可以注册多个变量的地址。 就跟一个对象可以同时付给多个附有__weak修饰符的变量。也就是说,如果你用一个废弃对象的地址作为键值来检索,你能够告诉的获取对应的附有__weak修饰符的变量的地址。

当释放对象的时候,废弃掉谁都不持有的对象,程序后续还会出现动作:
1、objc_release
2、因为引用计数为0, 所以执行dealloc
3、_objc_rootDealloc
4、object_dispose
5、objc_destructInstance
6、objc_clear_deallocating

最后调用的 objc_clear_deallocating 函数会出现如下动作:
1、从weak表中获取废弃对象的地址为键值的记录。
2、将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil。
3、从weak表中删除该记录。
4、从引用计数表中删除废弃对象的地址为键值的记录。

使用附有 __weak 修饰符的变量,即是使用注册到 autoreleasepool 中的对象。
id __weak obj1 = obj 可以转换为如下形式:

id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
objc_destroyWeak(&obj1);

这里增加了objc_loadWeakRetained和objc_autorelease的调用。
1、objc_loadWeakRetained:函数取出附有__weak修饰符变量所引用的对象,并retain。
2、objc_autorelease函数将对象注册到autoreleasepool中。

最后提醒的是:id __weak obj = [[NSObject alloc] init];  
        和  id __unsafe_unretained obj = [[NSObject alloc] init]; 
        
这样是不可以的,前者是因为不能持有对象,后者是obj被赋予的是 悬垂指针。  
虽然在arc中不会造成内存泄露,但是还是不要这样使用的好。

11. 引用计数

二、 Blocks

1. Blocks 是 C 语言的扩充功能。

一句话概括 Blocks 的扩充功能:带有自动变量(局部变量)的匿名函数。匿名函数就是不带有名称的函数。

2. C 语言的函数中可能使用的变量:

自动变量(局部变量)
函数的参数
静态变量(静态局部变量)
静态全局变量
全局变量

3. 截获自动变量

4. Block 与 __block 变量的实质

Block 类及其对应的存储域如下:


Block 类及其对应的存储域.jpeg

Block 的副本


Block 的副本.jpeg

Block 从栈复制到堆时对 __block 变量产生的影响


Block 从栈复制到堆时对 __block 变量产生的影响.jpeg

复制 __block 变量

复制 __block 变量.jpeg

什么时候栈上的Block会被复制到堆上呢?

5. Block 循环引用

原因:Block中附有__strong修饰符的对象类型自动变量在从栈复制到堆上时,该对象会被Block所持有。

解决方案:

ARC:通过 __weak 或 __unsafe_unretained 修饰符(iOS4)来替代 __strong 类型的被截获的自动变量通过 __block 说明符和设置nil来打破循环
MRC:通过 __block 说明符指定变量不被Block所retain;ARC下__block说明符的作用仅限于使其能在Block中被赋值。

如果对block做一次copy操作, block的内存就会在堆中

GCD

Grand Central Dispatch,是 iOS 目前最常用的多线程处理技术,在此之前一般使用 NSObject 类的 performSelector 系列方法或者 NSTherd 相关方法实现多线程。

1. 使用多线程的弊端

  1. 多个线程更新相同的资源会导致数据的不一致(数据竞争)
  2. 停止等待事件的线程会导致多个线程相互持续等待(死锁)
  3. 使用太多线程会消耗大量内存

2. GCD 相关 API

GCD 的 API

3. Dispatch Queue 的实现依托于:

  1. 用于管理追加的 Block 的 C 语音层实现的 FIFO 队列;
  2. Atomic 函数中实现的用于排他控制的轻量级信号
  3. 用于管理线程的 C 语音层实现的一些容器
    除此之外,GCD 是依托于系统内核级的实现,如下图:


    用于实现 Dispatch Queue 而使用的软件组件

4. Dispatch Source

它是BSD系内核惯有功能kqueue的包装。kqueue是在XUN内核中发生各种事件时,在应用程序编程方执行处理的技术。 
其CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生的各种事件的方法中最优秀的一种。

Dispatch Source 的种类入下图:


Dispatch Source 的种类
上一篇 下一篇

猜你喜欢

热点阅读