workiOS资料ios开发笔记

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

2016-06-05  本文已影响5421人  SvenLearn

工具:利用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规则
// 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];
属性

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

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

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

ARC实现

__strong

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

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

修饰符功能:

1)从weak表中获取废弃对象的地址作为键值得记录;
2)将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil;
3)从weak表删除该记录;
4)从引用计数表中删除废弃对象的地址为键值的记录。
1) objc_loadWeakRetained函数取出附有__weak修饰符的变量所引用的对象并retain;
2) objc_autorelease函数将对象注册到autoreleasepool中。

不能使用__weak修饰符的情况

 - (BOOL)allowsWeakReference;
 - (BOOL)retainWeakReference;
__autoreleasing修饰符

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


2. Blocks

实现

三种类型

实际上当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和__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能够调度的事件源分类
上一篇 下一篇

猜你喜欢

热点阅读