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

工具:利用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能够调度的事件源分类
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容