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

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


Objective-C高级编程:iOS与OS X多线程和内存管理_第1张图片

另类总结:
四类关键字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)结合引用计数表进行,


Objective-C高级编程:iOS与OS X多线程和内存管理_第2张图片

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


Objective-C高级编程:iOS与OS X多线程和内存管理_第3张图片

1. 自动引用计数

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

引用计数/内存管理

对照明设备所做的工作 对OC对象所做的动作
开灯 生成对象
需要照明 持有
不需要照明 释放
关灯 废弃
Objective-C高级编程:iOS与OS X多线程和内存管理_第4张图片
内存管理的思考方式 对应OC方法
自己生成的对象,自己所持有 alloc/new/copy/mutableCopy等
非自己生成的对象(比如[NSArray array]),自己也能持有 retain
1. 不再需要自己持有的对象时释放
2. 无妨释放非自己持有的对象(比如多次release)
release
当对象不被任何其他对象持有时废弃 dealloc

苹果的实现

Objective-C高级编程:iOS与OS X多线程和内存管理_第5张图片
基于内存块地址-引用计数的哈希散列表进行管理
=> 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实现
Objective-C高级编程:iOS与OS X多线程和内存管理_第6张图片

autorelease方法的IMP Caching


Objective-C高级编程:iOS与OS X多线程和内存管理_第7张图片
autorelease的实现

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

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

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

所有权修饰符

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

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


Objective-C高级编程:iOS与OS X多线程和内存管理_第8张图片

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;
Objective-C高级编程:iOS与OS X多线程和内存管理_第9张图片
数组

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

ARC实现

__strong

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

Objective-C高级编程:iOS与OS X多线程和内存管理_第10张图片
在其他情况时,编译器会进行优化

__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万能。

Objective-C高级编程:iOS与OS X多线程和内存管理_第11张图片

Objective-C高级编程:iOS与OS X多线程和内存管理_第12张图片
Block的废弃和__block变量的释放

实质/本质

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

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


Objective-C高级编程:iOS与OS X多线程和内存管理_第13张图片

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)

Objective-C高级编程:iOS与OS X多线程和内存管理_第14张图片
两种Queue

GCD API

Objective-C高级编程:iOS与OS X多线程和内存管理_第15张图片

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

Objective-C高级编程:iOS与OS X多线程和内存管理_第16张图片
队列类型和转发

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


Objective-C高级编程:iOS与OS X多线程和内存管理_第17张图片

GCD实现

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

Dispatch Queue

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

Objective-C高级编程:iOS与OS X多线程和内存管理_第18张图片
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) - 启动事件源监听
Objective-C高级编程:iOS与OS X多线程和内存管理_第19张图片
GCD能够调度的事件源分类

你可能感兴趣的:(Objective-C高级编程:iOS与OS X多线程和内存管理)