听说这本书很好,所以在项目不怎么忙的时候就读了读。总结了点笔记。
859001-7ceabf4418ec5228.png手动内存管理MRC
- 内存管理的思想
思想一:自己生成的对象,自己持有。
思想二:非自己生成的对象,自己也能持有。
思想三:不再需要自己持有的对象时释放对象。
思想四:非自己持有的对象无法释放。
从上面的思想来看,我们对对象的操作可以分为三种:生成,持有,释放,再加上废弃,一共有四种。它们所对应的Objective-C的方法和引用计数的变化是:
思想一:自己生成的对象,自己持有
alloc
new
copy
mutableCopy
如 id obj = [[NSObject alloc] init];//持有新生成的对象
思想二:非自己生成的对象,自己也能持有
id obj = [NSMutableArray array];//非自己生成并持有的对象
[obj retain];//持有新生成的对象
来看看[NSMutableArray array]是怎么个实现法,和自己生成的对象有什么区别:
- (id)object
{
id obj = [[NSObject alloc] init];//持有新生成的对象
[obj autorelease];//自动释放
return obj;
}
通过autorelease方法,使对象的持有权转移给了自动释放池。所以实现了:调用方拿到了对象,但这个对象还不被调用方所持有.
E33F7638-D143-45FA-BEBA-FBCCC0264E93.png思想三:不再需要自己持有的对象时释放对象
id obj = [[NSObject alloc] init];//持有新生成的对象
[obj release];//事情做完了,释放该对象
或者是
id obj = [NSMutableArray array];//非自己生成并持有的对象
[obj retain];//持有新生成的对象
[obj release];//事情做完了,释放该对象
思想四:无法释放非自己持有的对象
情况一:多次释放。
id obj = [[NSObject alloc] init];//持有新生成的对象
[obj release];//释放该对象,不再持有了
[obj release];//释放已经废弃了的对象,崩溃
情况二:对象不被自己持有,就释放。
id obj = [NSMutableArray array];//非自己生成并持有的对象
[obj release];//释放了非自己持有的对象
实现原理
Objective-C对象中保存着引用计数这一整数值。
调用alloc或者retain方法后,引用计数+1。
调用release后,引用计数-1。
引用计数为0时,调用dealloc方法废弃对象。
autorelease
autorelease的具体使用方法如下:
生成并持有NSAutoreleasePool对象。
调用已分配对象的autorelease方法。
废弃NSAutoreleasePool对象。
所有调用过autorelease方法的对象,在废弃NSAutoreleasePool对象时,都将调用release方法(引用计数-1)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//相当于obj调用release方法
NSRunLoop在每次循环过程中,NSAutoreleasePool对象都会被生成或废弃.
096923F9-158F-4836-846C-DB293855CA83.png这样会有个问题如果是生成大量的autorelease对象,只要不废弃NSAutoreleasePool的话对象就不能释放,所以会产生内存不足的问题。由上图NSRunLoop是在应用程序主线程处理完了才会废弃Pool。所以如果在for循环里创建好多的局部对象,他们得不到及时的释放,就会使得程序因为内存不足奔溃。
如
for (int i = 0; i < 10000; i++)
{
图像文件读入到data对象,
data生成UIimage对象,
改变对象的尺寸生成一个新的UIimage对象
}
解决办法:
for (int i = 0; i < 10000; i++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
图像文件读入到data对象,
data生成UIimage对象,
改变对象的尺寸生成一个新的UIimage对象
[pool drain];//相当于obj调用release方法
}
每个循环都会创建个池子,池子也是循环完了就释放。所以自动变量也都释放了。自动变量都是加在离它最近的池子的。
苹果的实现
class AutoreleasePoolPage
{
static inline void *push()
{
//生成或者持有 NSAutoreleasePool 类对象
}
static inline void pop(void *token)
{
//废弃 NSAutoreleasePool 类对象
releaseAll();
}
static inline id autorelease(id obj)
{
//相当于 NSAutoreleasePool 类的 addObject 类方法
AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 实例;
autoreleaesPoolPage->add(obj)
}
id *add(id obj)
{
//将对象追加到内部数组中
}
void releaseAll()
{
//调用内部数组中对象的 release 方法
}
};
//压栈
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//出栈
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
id *objc_autorelease(id obj)
{
return AutoreleasePoolPage::autorelease(obj);
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 等同于 objc_autoreleasePoolPush
id obj = [[NSObject alloc] init];
[obj autorelease];
// 等同于 objc_autorelease(obj)
[pool drain];
// 等同于 objc_autoreleasePoolPop(pool)
想要了解更多autoreleasepool原理看这个http://blog.leichunfeng.com/blog/2015/05/31/objective-c-autorelease-pool-implementation-principle/
ARC下的内存管理
在ARC机制下,编译器就可以自动进行内存管理,减少了开发的工作量。但我们有时仍需要四种所有权修饰符来配合ARC来进行内存管理。
四种所有权修饰符
ARC提供四种修饰符,分别是strong, weak, autoreleasing, unsafe_unretained
__strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。
NSObject *obj = [[NSObject alloc]init];
他们是等价的
NSObject *__strong obj = [[NSObject alloc]init];
obj = nil;
__weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。
比如避免循环引用的弱引用声明:
__weak __typeof(self) weakSelf = self;
_autoreleasing:将对象赋值给附有 _ autoreleasing 修饰符的变量等同于ARC 无效时调用对象的autorelease方法
@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
unsafe_unretained:相当于assign。直接赋值。引用计数不变。他会发生野指针现象。所以不安全。不像weak,当指向的对象为空的时候,将指针置为nil。
属性的内存管理
ObjC2.0引入了@property,提供成员变量访问方法、权限、环境、内存管理类型的声明,下面主要说明ARC中属性的内存管理
属性的参数分为三类,基本数据类型默认为(atomic,readwrite,assign),对象类型默认为(atomic,readwrite,strong),其中第三个参数就是该属性的内存管理方式修饰,修饰词可以是以下之一:
assign:直接赋值
assign一般用来修饰基本数据类型
@property (nonatomic, assign) NSInteger count;
当然也可以修饰ObjC对象,但是不推荐,因为被assign修饰的对象释放后,指针还是指向释放前的内存,在后续操作中可能会导致内存问题引发崩溃。
retain
retain和strong一样,都用来修饰ObjC对象
使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值一样时导致对象被释放的的问题
MRC写法如下
- (void)setCount:(NSObject *)count {
[count retain];
[_count release];
_count = count;
}
ARC对应写法
- (void)setCount:(NSObject *)count {
_count = count;
}
copy
一般用来修饰String、Dict、Array等需要保护其封装性的对象,尤其是在其内容可变的情况下,因此会拷贝(深拷贝)一份内容給属性使用,避免可能造成的对源内容进行改动。
使用set方法赋值时,实质上是会先拷贝新值,再释放旧值,再设置新值。
实际上,遵守NSCopying的对象都可以使用copy,当然,如果你确定是要共用同一份可变内容,你也可以使用strong或retain。
weak:ARC新引入修饰词,可代替assign,比assign多增加一个特性(置nil)
weak和strong一样用来修饰ObjC对象。
使用set方法赋值时,实质上不保留新值,也不释放旧值,只设置新值。
@property (weak) id<MyDelegate> delegate;
strong:
ARC新引入修饰词,可代替retain.ARC一般都写strong。
block的内存管理
OS中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:
1)如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。
@property (nonatomic, copy) void(^block)(NSData * data);
block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。
__weak typeof(self) weakSelf = self;
循环中对象占用内存大
这个问题常见于循环次数较大,循环体生成的对象占用内存较大的情景。
for (int i = 0; i < 10000; i ++) {
Person * p = [[Person alloc]init];
}
该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。
for (int i = 0; i < 10000; i ++) {
@autoreleasepool {
Person * p = [[Person alloc]init];
}
}
特殊情况
假如有10000张图片,每张2M左右,现在需要获取所有图片的尺寸,你会怎么做?
用imageNamed方法加载图片默认会写到缓存里,autoReleasePool也不能释放缓存,对此问题需要另外的解决方法
179360B6-0EE5-4DCD-8BBA-9A0C801F2B8D.png
网友评论