美文网首页workiOS资料ios开发笔记
Objective-C高级编程:iOS与OS X多线程和内存管理

Objective-C高级编程:iOS与OS X多线程和内存管理

作者: SvenLearn | 来源:发表于2016-06-05 12:00 被阅读5421次

    工具:利用clang(LLVM编译器)的命令:clang -rewrite-objc 源代码文件名 将OC转换成对应的C++源代码。



    另类总结:
    四类关键字alloc/new/copy/mutableCopy等 | retain | release | dealloc
    四种所有权修饰符__strong | __weak | __unsafe_unretained | __autoreleasing
    两张散列表(引用计数表和weak表)+ 一个动态数组autoreleasepool)+NSRunLoop
    属性assign | copy | retain | strong | unsafe_unretained | weak
    Toll-Free Bridge - 传送门

    思路串联:
    MRC下,内存需要人工管理,通过alloc等四类关键字(本质:calloc、free)结合引用计数表进行,


    参考:块替代传统回调函数或delegate的意义



    1. 自动引用计数

    在LLVM【编译器】中设置ARC为有效状态,就无需键入retain或release代码了,编译器将结合【OC运行时】基于引用计数自动进行内存管理

    引用计数/内存管理

    对照明设备所做的工作 对OC对象所做的动作
    开灯 生成对象
    需要照明 持有
    不需要照明 释放
    关灯 废弃
    内存管理的思考方式 对应OC方法
    自己生成的对象,自己所持有 alloc/new/copy/mutableCopy等
    非自己生成的对象(比如[NSArray array]),自己也能持有 retain
    1. 不再需要自己持有的对象时释放<br />2. 无妨释放非自己持有的对象(比如多次release) release
    当对象不被任何其他对象持有时废弃 dealloc

    苹果的实现

    基于内存块地址-引用计数的哈希散列表进行管理
    => alloc/retain/retainCount/release/dealloc实现

    alloc => 调用class_createInstance(calloc)分配内存 => 设置isa指针和成员变量初始值(0) => 在引用计数表中添加纪录,并将引用计数值置为1

    case OPERATION_retain:
        CFBasicHashAddValue( table, obj );
        return obj;
    case OPERATION_retainCount:
        count = CFBasicHashGetCountOfKey( table, obj );
        return count;
    case OPERATION_release:
        count = CFBasicHashRemoveValue( table, obj );
        return 0 == count;
    

    dealloc => 删除引用计数表中的对应记录 => free内存块

    => autorelease实现

    autorelease方法的IMP Caching


    autorelease的实现

    注意:无论调用哪一个对象的autorelease实例方法,实际上调用的都是NSObject类的autorelease实例方法。(NSAutoreleasePool类的autorelease实例方法被重载了,运行时会报错!!!)

    ARC - 只是自动地帮助我们处理“引用计数”的相关部分。

    文件的编译属性设置:-fobjc-arc-fno-objc-arc

    所有权修饰符

    @autoreleasepool{}块替代了NSAutoreleasePool类对象的生成持有和废弃

    __autoreleasing修饰符替代了autorelease方法的调用


    autoreleasepool自动注册
    不以alloc/new/copy/mutableCopy开头的方法(init系列方法除外)返回的对象将自动注册
    id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。
    对象指针型赋值时,所有权修饰符必须一致。

    id __autoreleasing *obj;
    NSObject * __autoreleasing *obj;
    

    拓展:附有__strong/__weak修饰符的变量类似于C++中的智能指针std::shared_ptr和std::weak_ptr。

    ARC规则
    • ……
    • 须遵守内存管理方法的命名规则
    • 以alloc/new/copy/mutableCopy名称开头的方法必须返回给调用方所应当持有的对象
    • 以init开始的方法必须是返回类型为id/class/superclass/subclass的实例方法
    • ……
    • 显示转换id和void *
    • CF对象与OC对象的转换不需要使用额外的CPU资源,所以被称为Toll-Free Bridge
    // ARC: void *p = (__bridge_retained void *)obj;
    CFTypeRef CFBridgeRetain(id X) {
           return (__bridge_retained CFTypeRef)X;
    }
    // MRC
    id obj = [[NSObject alloc] init];
    void *p = obj;
    [(id)p retain];
     
    // ARC: id obj = (__bridge_transfer id)p;
    id CFBridgeRelease(CFTypeRef X) {
           return (__bridge_transfer id)X;
    }
    // MRC
    id obj = (id)p;
    [obj retain];
    [(id)p release];
    
    • CF还有以下方法:CFRetainCFReleaseCFGetRetainCountCFShow
    属性

    属性的特性修饰符必须和对应成员变量的所有权修饰符一致!!!

    // weak和默认的__strong冲突了!!!
    @property (nonatomic, weak) id obj;
    
    数组

    ???必须将nil赋值给所有数组元素,使得元素所赋值对象的强引用失效,从而释放那些对象;然后再使用free函数废弃内存块,否则会有内存泄漏!!!

    ARC实现

    __strong

    赋值分为两种情况:alloc/new/copy/mutableCopy系列和其他
    涉及的函数有:objc_msgSendobjc_releaseobjc_retainobjc_autorelease

    在其他情况时,编译器会进行优化
    __weak

    修饰符功能:

    • 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量;
    • 对象废弃时最后调用的objc_clear_deallocating函数的动作如下
    1)从weak表中获取废弃对象的地址作为键值得记录;
    2)将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil;
    3)从weak表删除该记录;
    4)从引用计数表中删除废弃对象的地址为键值的记录。
    
    • 使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象;
    • 源代码解读
    1) objc_loadWeakRetained函数取出附有__weak修饰符的变量所引用的对象并retain;
    2) objc_autorelease函数将对象注册到autoreleasepool中。
    
    • 最佳实践:使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后再使用;从而避免对象多次注册到autoreleasepool中。

    不能使用__weak修饰符的情况

    • iOS4及以下
    • 通过 NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE 声明了不支持的类,比如 NSMachPort
    • 以下方法返回NO的时候:
     - (BOOL)allowsWeakReference;
     - (BOOL)retainWeakReference;
    
    __autoreleasing修饰符

    等同于ARC无效时调用对象的autorelease方法,即 objc_autorelease 方法的调用。


    2. Blocks

    • 语法:完整形式(^T (…) { … })=> 基于推断省略返回类型(^ (…) { … })=> 省略参数(^ { … }
      很像函数指针,
    • 变量使用:使用typedef提高可读性
    • 截获自动变量
    • 赋值导致编译错误(Mutable类的add方法不会!) => 解决方案:使用 __block 说明符
    • 不能截获C语言数组 => 解决方案:使用指针

    实现

    • 本质 OC对象(结构体)
      • isa 类结构指针 和 三大类型 _NSConcrete[ Stack | Malloc | Global ]Block
      • FuncPtr 函数指针
      • DescFlags 和其他
    • 截获自动变量 - 只针对Block中使用的自动变量
      • __cself 和 OC中的 self、C++中的 this
      • 自动变量的值以成员变量的形式被保存到Block的结构体实例(或者说被其持有),通过__cself被使用;如果是__block变量,则转化成结构体,其指针作为成员变量保存到Block结构体中
      • 在Block中修改自动变量的两种方法:
        1. 静态变量、静态全局变量或全局变量
        2. __block存储域类说明符 - 类似于static、auto和register说明符,指定将变量值设置到哪个存储域中。

    三种类型

    • _NSConcreteGlobalBlock - 存储域:程序的数据区域
      通过以下情况得到实例
      1.记述全局变量的地方有Block语法时
      2.Block语法的表达式中不使用截获的自动变量时
    • _NSConcreteStackBlock - 存储域:栈;复制效果:到堆
      除Global之外的Block语法生成的都是栈Block
    • _NSConcreteMallocBlock - 存储域:堆;复制效果:引用计数增加

    实际上当ARC有效时,多数情况编译器会恰当地判断,自动生成将Block从栈上复制到堆上的代码!
    什么时候栈上的Block会被复制到堆上呢?
     1. 调用Block的copy实例方法时
     2. Block作为函数返回值返回时
     3. 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
     4. 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
    需要手动复制的情形:NSArray的initWithObjects:(除4外作为方法参数时;注释:现在应该连这个也OK了,请测试!!!);也即是ARC万能。


    Block的废弃和__block变量的释放

    实质/本质

    • Block - 栈/堆/数据区上的 Block 的结构体实例,isa指针
    • __block 变量 - 栈上 __block 变量的结构体实例

    Block超出变量作用域可存在的理由 => 将Block和__block变量从栈上复制到堆上解决
    __block变量的结构体成员变量__fowarding存在的理由 => 实现无论__block变量配置在栈上还是堆上都能正确地进行访问


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

    1. ARC:通过 __weak__unsafe_unretained 修饰符(iOS4)来替代 __strong 类型的被截获的自动变量
      通过 __block 说明符和设置nil来打破循环
    2. MRC:通过 __block 说明符指定变量不被Block所retain;ARC下__block说明符的作用仅限于使其能在Block中被赋值。
    "原理"
    如果对block做一次copy操作, block的内存就会在堆中
    * 它会对所引用的对象做一次retain操作
    * 非ARC : 如果所引用的对象用了__block修饰, 就不会做retain操作
    * ARC : 如果所引用的对象用了__unsafe_unretained\__weak修饰, 就不会做retain操作
    

    3. Grand Central Dispatch(GCD)

    两种Queue

    GCD API

    获取系统提供的队列:Main/Global Dispatch Queue;无需内存管理

    队列类型和转发

    使用 Concurrent Dispatch Queue 和 dispatch_barrier_async 函数可实现高效率的数据库访问和文件访问。


    GCD实现

    GCD分为Dispatch Queue和Dispatch Source两个部分,各自的实现如下:

    Dispatch Queue

    GCD是XNU内核级所实现的多线程管理API,根据CPU核等系统软硬件情况进行了优化的线程池,提供高性能的简单编程接口。
    (注释:Darwin - NeXT电脑公司开发的用于NEXTSTEP的XNU内核是兼有Mach3微内核和大量来自BSD宏内核的元素(进程、网络、虚拟文件系统)以及I/O Kit的混合内核)

    Dispatch Queue实现
    Dispatch Source

    实现:BSD系内核惯有功能kqueue的包装(XNU内核事件发生时,能在应用程序编程方执行处理)。

    "使用惯例"   
    1. create or get - 获取队列
    2. dispatch_source_create - 基于“监听”的内核事件在队列上构建Dispatch Source
    3. dispatch_source_set_( timer|event_handler|cancel_handler ) - 配置Dispatch Source
      一些列的处理方法,比如:dispatch_source_get_data、dispatch_source_cancel、dispatch_source_release
    4. dispatch_resume(source) - 启动事件源监听
    
    GCD能够调度的事件源分类

    相关文章

      网友评论

      本文标题:Objective-C高级编程:iOS与OS X多线程和内存管理

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