美文网首页iOS备忘录iOS-内存管理iOS知识点面试
《Objective-C 高级编程 iOS 与 OS X 多线程

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

作者: 赫子丰 | 来源:发表于2019-10-14 11:19 被阅读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 有效情况下的编码规则:

    • 不能使用retain/release/retainCount/autorelease
    • 不能使用NSAllocateObject/NSDeallocateObject
    • 必须遵守内存管理的方法命名规则
    • 不要显式调用dealloc
    • 使用@autoreleasepool块替代NSAutoreleasePool
    • 不能使用区域NSZone
    • 对象型变量不能作为c语言结构体的成员
    • 显式转换id和void*

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

    • assign: __unsafe_unretained
    • copy: __strong(但是赋值的是被复制的对象)
    • retain: __strong
    • strong: __strong
    • unsafe_unretained: __unsafe_unretained
    • weak: __weak

    9. ARC 的实现

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

    • clang (LLVM 编译器)3.0以上
    • objc4 Objective-C 运行时库493.9以上

    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. 引用计数

    • 获取引用计数值的函数:uintptr_t_objc_rootRetainCount(id obj) , 这个函数可以获取指定对象的引用计数值(ARC中,retainCount已经不能用了),但是也不能完全信任该函数取得的值,对于已经释放的对象以及不正确的对象地址,有时也会返回 1;
    • 函数 _objc_autoreleasePoolPrint函数会观察注册到autoreleasepool中的引用对象。

    二、 Blocks

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

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

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

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

    3. 截获自动变量

    • 只针对Block中使用的自动变量
    • 自动变量的值以成员变量的形式被保存到Block的结构体实例(或者说被其持有),通过__cself被使用;如果是__block变量,则转化成结构体,其指针作为成员变量保存到Block结构体中

    4. Block 与 __block 变量的实质

    • Block:栈上 Block 的结构体实例
    • __block:栈上 __block 变量的结构体实例

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


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

    Block 的副本


    Block 的副本.jpeg

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


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

    复制 __block 变量

    复制 __block 变量.jpeg

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

    • 调用Block的copy实例方法时
    • Block作为函数返回值返回时
    • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
    • 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时

    5. Block 循环引用

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

    解决方案:

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

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

    • 它会对所引用的对象做一次retain操作
    • 非ARC : 如果所引用的对象用了__block修饰, 就不会做retain操作
    • ARC : 如果所引用的对象用了__unsafe_unretained__weak修饰, 就不会做retain操作

    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 的种类

    相关文章

      网友评论

        本文标题:《Objective-C 高级编程 iOS 与 OS X 多线程

        本文链接:https://www.haomeiwen.com/subject/dvpauctx.html