要打开监视僵尸对象
内存管理简介
-
内存管理的管理范围
- 任何继承了NSObject的对象
-
只有OC对象才需要进行内存管理的本质原因
- OC对象存放于堆里面
- 非OC对象一般放在栈里面(栈内存会被系统自动回收)
-
栈(操作系统):由操作系统自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈(先进后出);
-
堆(操作系统):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。
引用计数器
-
什么是引用计数器
- 每个OC对象都有自己的引用计数器
- 它是一个整数(4个字节)
- 从字面上, 可以理解为”对象被引用的次数”
- 也可以理解为: 它表示有多少人正在用这个对象,多少个指针指向这个对象
-
当没有任何人使用这个对象时, 系统才会回收这个对象, 也就是说
- 当对象的引用计数器为0时,对象占用的内存就会被系统回收
- 如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收(除非整个程序已经退出 )
-
任何一个对象, 刚生下来的时候, 引用计数器都为1
- 当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1
-
引用计数器的常见操作
- 给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
- 给对象发送一条release消息, 可以使引用计数器值-1
-
自动引用计数
-
ARC: Automatic(自动) Reference(引用) Counting(计数)
-
什么是自动引用计数?
-
不需要程序员管理内容, 编译器会在适当的地方自动给我们添加release/retain等代码
-
注意点: OC中的ARC和java中的垃圾回收机制不太一样, java中的垃圾回收是系统干得, 而OC中的ARC是编译器干得
-
野指针&僵尸对象&空指针
-
僵尸对象
- 已经被销毁的对象(不能再使用的对象)
-
野指针
- 指向僵尸对象(不可用内存)的指针
- 给野指针发消息会报EXC_BAD_ACCESS错误
-
空指针
p = nil;-
没有指向存储空间的指针(里面存的是nil, 也就是0)
-
给空指针发消息是没有任何反应的
-
为了避免野指针错误的常见办法
- 在对象被销毁之后, 将指向对象的指针变为空指针
-
引用计数器与内存管理
- 内存管理原则
-
谁创建谁release :
- 如果你通过alloc、new、copy或mutableCopy来创建一个对象,那么你必须调用release或autorelease
-
谁retain谁release:
- 只要你调用了retain,就必须调用一次release
-
多个对象
- 只要还有人在用某个对象,那么这个对象就不会被回收
- 只要你想用这个对象,就让对象的计数器+1
- 当你不再使用这个对象时,就让对象的计数器-
-
set方法内存管理
(1)retain需要使用的对象
(2)release之前的对象
(3)只有传入的对象和之前的不同才需要release和retain
- (void)setRoom:(Room *)room
{
// 避免指针重复指向,过度释放
if (room != _room)
{
//指针更换指向
// 对当前正在使用的房间(旧房间)做一次release
[_room release];
// 对新房间做一次retain操作
_room = [room retain];
}
}
dealloc方法的内存管理
-
当一个对象的引用计数器值为0时,这个对象即将被销毁,其占用的内存被系统回收
-
对象即将被销毁时系统会自动给对象发送一条dealloc消息 (因此, 从dealloc方法有没有被调用,就可以判断出对象是否被销毁)
-
dealloc方法的重写
- 一般会重写dealloc方法,在这里释放相关资源,dealloc就是对象的遗言
一旦重写了dealloc方法, 就必须调用[super dealloc],并且放在最后面调用
-
使用注意
- 不能直接调用dealloc方法
- 一旦对象被回收了(僵尸对象), 它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
- 当A对象释放的时候, 一定要对B对象进行一次release, 这样才能保证A对象释放了, B对象也会随之释放, 避免内存泄露
- (void)dealloc
{
// 当人不在了,代表不用房间了
// 对房间做一次release操作
[_room release];
NSLog(@"%s", __func__);
[super dealloc];
}
//如果微博中有用户, 用户中有账号(微博最高级),微博的dealloc如下
- (void)dealloc
{
NSLog(@"%s", __func__);
/*
[_text release];
_text = nil;
[_picture release];
_picture = nil;
[_author release];
_author = nil;
[_repostStatus release];
_repostStatus = nil;
*/
// 下面这句话相当于调用了set方法
// 先release旧值, 然后再将新值赋值给属性
self.text = nil;
self.picture = nil;
self.author = nil;
self.repostStatus = nil;
[super dealloc];
}
autorelease(延迟释放机制)
@autoreleasepool
{ //开始代表创建自动释放池
} //结束代表销毁自动释放池
- autorelease是一种支持引用计数的内存管理方式,只要给对象发送一条autorelease消息,会将对象放到一个自动释放池中,当自动释放池被销毁时,会对池子里面的
所有对象做一次release操作
@autoreleasepool{
//调用完autorelease方法后,对象的计数器不变
Person *p = [Person new];
p = [p autorelease];
//或者
Person *p = [[[Person alloc] init] autorelease];
}
注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该对象依然不会被释放。
- 在iOS程序运行过程中,会创建无数个池子(自动释放池可以嵌套使用)。这些池子都是以栈结构存在(先进后出,最后创建的自动释放池,会放到栈顶,也最先释放)
@autoreleasepool { // 栈底自动释放池
@autoreleasepool {
@autoreleasepool { // 栈顶自动释放池
Person *p = [[[Person alloc] init] autorelease];
}
Person *p = [[[Person alloc] init] autorelease];
}
}
//自动释放池中不适宜放占用内存比较大的对象
- 应用
+ (instancetype)person
{
return [[[self alloc] init] autorelease];
}
通过类方法创建的基本都是ARC:NSMutableArray *arrM = [NSMutableArray array];
ARC(编译器特性)
-
Automatic Reference Counting,自动引用计数,即ARC。当ARC开启时,编译器将自动在代码合适的地方插入retain, release和autorelease,[super dealloc]
-
ARC是编译器特性,而不是运行时特性
-
ARC的判断原则
- 只要还有一个强指针变量指向对象,对象就会保持在内存中
-
强指针
- 默认所有指针变量都是强指针
- 被__strong修饰的指针
Person *p1 = [[Person alloc] init];
__strong Person *p2 = [[Person alloc] init];
-
弱指针
- 被__weak修饰的指针
__weak Person *p = [[Person alloc] init];
//千万不要使用弱指针保存新创建的对象
// p是弱指针, 对象会被立即释放
__weak Person *p1 = [[Person alloc] init];
注意:当使用ARC的时候,暂时忘记“引用计数器”,因为判断标准变了。
- 循环引用问题 ARC和MRC一样, 如果A拥有B, B也拥有A, 那么必须一方使用弱指针
@interface Person : NSObject
//@property (nonatomic, retain) Dog *dog;
@property (nonatomic, strong) Dog *dog;
@end
@interface Dog : NSObject
// 错误写法, 循环引用会导致内存泄露
//@property (nonatomic, strong) Person *owner;
// 正确写法, 当如果保存对象建议使用weak
//@property (nonatomic, assign) Person *owner;
@property (nonatomic, weak) Person *owner;
@end
集合内对象的内存管理
- 集合本身不是对象,不需要进行retain release操作
- 如果将一个对象添加到一个数组中, 那么数组会对对象进行一个retain
[arrM addObject:p]; - 当数组对象释放之后, 会给数组中所有的对象发送一条release消息
[arrM release]; - 当数组移除一个对象之后, 会给这个对象发送一条release消息
[arrM removeObject:p];
集合对象内存管理总结
-
1.官方内存管理原则
- 1> 当调用alloc、new、copy(mutableCopy)方法产生一个新对象的时候,就必须在最后调用一次release或者autorelease
- 2> 当调用retain方法让对象的计数器+1,就必须在最后调用一次release或者autorelease
-
2.集合的内存管理细节
- 1> 当把一个对象添加到集合中时,这个对象会做了一次retain操作,计数器会+1
- 2> 当一个集合被销毁时,会对集合里面的所有对象做一次release操作,计数器会-1
- 3> 当一个对象从集合中移除时,这个对象会一次release操作,计数器会-1
-
3.普遍规律
-
1> 如果方法名是add\insert开头,那么被添加的对象,计数器会+1
-
2> 如果方法名是remove\delete开头,那么被移除的对象,计数器-1
-
Copy
copy基本概念
-
OC中的copy
- 作用:利用一个源对象产生一个副本对象
-
特点:
- 修改源对象的属性和行为,不会影响副本对象
- 修改副本对象的属性和行为,不会影响源对象
Copy的使用
-
如何使用copy功能
- 一个对象可以调用copy或mutableCopy方法来创建一个副本对象
- copy : 创建的是不可变副本(如NSString、NSArray、NSDictionary)
- mutableCopy :创建的是可变副本(如NSMutableString、NSMutableArray、NSMutableDictionary)
- 与被拷贝的类无关,可变不可变取决于copy,mutableCopy
-
使用copy功能的前提
- copy : 需要遵守NSCopying协议,实现copyWithZone:方法
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
-
使用mutableCopy的前提
- 需要遵守NSMutableCopying协议,实现mutableCopyWithZone:方法
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(NSZone *)zone;
@end
自定义对象实现copy
// 1.以后想让自定义的对象能够被copy只需要遵守NSCopying协议
// 2.实现协议中的- (id)copyWithZone:(NSZone *)zone
// 3.在- (id)copyWithZone:(NSZone *)zone方法中创建一个副本对象, 然后将当前对象的值赋值给副本对象即可
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCopying, NSMutableCopying>
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
@end
#import "Person.h"
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
// 1.创建一个新的对象
Person *p = [[[self class] allocWithZone:zone] init];
// 2.设置当前对象的内容给新的对象
p.age = _age;
p.name = _name;
// 3.返回新的对象
return p;
}
- (id)mutableCopyWithZone:(NSZone *)zone
{
// 1.创建一个新的对象
Person *p = [[[self class] allocWithZone:zone] init];
// 2.设置当前对象的内容给新的对象
p.age = _age;
p.name = _name;
// 3.返回新的对象
return p;
}
@end
#import "Person.h"
@interface Student : Person
@property (nonatomic, assign) double height;
@end
#import "Student.h"
@implementation Student
- (id)copyWithZone:(NSZone *)zone
{
id obj = [super copyWithZone:zone];
// 2.设置数据给副本
[obj setHeight:_height];
// 3.返回副本
return obj;
}
@end
深拷贝和浅拷贝
-
浅复制(浅拷贝,指针拷贝,shallow copy)
- 源对象和副本对象是同一个对象
- 源对象引用计数器+1,相当于做一次retain操作
- 本质是:没有产生新的对象
- 如果是通过不可变对象调用了copy方法, 那么不会生成一个新的对象
原因: 因为原来的对象是不能修改的, 拷贝出来的对象也是不能修改的
既然两个都不能修改, 所以永远不能影响到另外一个对象, 那么已经符合需求
所以: OC为了对内存进行优化, 就不会生成一个新的对象
-
深复制(深拷贝,内容拷贝,deep copy)
-
源对象和副本对象是不同的两个对象
-
源对象引用计数器不变,副本对象计数器为1(因为是新产生的)
-
本质是:产生了新的对象
-
为什么会产生一个新的对象
1.因为拷贝要求修改原来的对象不能影响到拷贝出来得对象,修改拷贝出来的对象也不能影响到原来的对象, 所以需要生成一个新的对象
2.由于以前的对象是一个不可变的对象, 而通过mutableCopy拷贝出来的对象必须是一个可变的对象, 所以必须生成一个新的对象
-
只有源对象和副本对象都不可变时,才是浅复制,其它都是深复制
一次alloc/retain/copy 对应一次release,深拷贝release新的对象,浅拷贝release原来的对象
//浅拷贝
NSString *str = [[NSString alloc]initWithFormat:@"lnj"];
NSLog(@"str = %lu", [str retainCount]);
//不会生成新的对象,但是需要注意,正是因为不会生产新的对象,所以系统会对以前的对象进行一次retain
//如果是浅拷贝,那么系统就会对原来的对象进行retain
NSString *str2 = [str copy];
NSLog(@"str = %lu", [str retainCount]);
[str release];
[str release];
//深拷贝:会生成新的对象,正是因为会生成新的对象,所以系统不会对以前的对象进行retain,但是因为生成了新的对象,所以我们需要对新的对象进行release
NSMutableString *strM = [str mutableCopy];
NSLog(@"str = %lu", [str retainCount]);
NSLog(@"strM = %lu", [strM retainCount]);
NSLog(@"%p - %p", str, strM);
[strM release];
[str release];
@property中的copy关键字
- 防止外界修改内部的数据
NSMutableString *temp = [NSMutableString stringWithFormat:@"lnj"];
Person *p = [[Person alloc] init];
p.name = temp;
// 问题: 修改了外面的变量, 影响到了对象中的属性
[temp appendString:@" cool"];
NSLog(@"name = %@", p.name);
// 记住: 以后字符串属性都用copy
//@property (nonatomic, copy) NSString *name;
block
// block默认存储在栈中, 栈中的block访问到了外界的对象, 不会对对象进行retain
// block如果在堆中, 如果在block中访问了外界的对象, 会对外界的对象进行一次retain(保证blcok内的外界对象存在,不会被提前释放)
Person *p = [[Person alloc] init];
NSLog(@"retainCount = %lu", [p retainCount]);//1
void (^myBlock)() = ^{
NSLog(@"%@", p);
NSLog(@"retainCount = %lu", [p retainCount]);//2
};
Block_copy(myBlock); // 将block转移到堆中
myBlock();
- 可以使用copy保存block, 这样可以保住block中使用的外界对象的命
// 注意: 如果是block使用copy并不是拷贝, 而是转移
//typedef void (^myBlock)();
//@property (nonatomic, copy) myBlock pBlock;
// 避免以后调用block的时候, 外界的对象已经释放了
Dog *d = [[Dog alloc] init];
NSLog(@"retainCount = %lu", [d retainCount]);//1
Person *p = [[Person alloc] init];
p.pBlock = ^{
NSLog(@"%@", d);
};
NSLog(@"retainCount = %lu", [d retainCount]); // 2
// 如果狗在调用block之前释放了, 那么程序就会崩溃
[d release];
NSLog(@"retainCount = %lu", [d retainCount]); // 1
p.pBlock();
[p release];
@implementation Person
- (void)dealloc
{
// 只要给block发送一条release消息, block中使用到的对象也会收到该消息
Block_release(_pBlock);
NSLog(@"%s", __func__);
[super dealloc];
}
@end
- 注意点: copy block之后引发循环引用
// 注意: 如果是block使用copy并不是拷贝, 而是转移
//typedef void (^myBlock)();
//@property (nonatomic, copy) myBlock pBlock;
// 如果对象中的block又用到了对象自己, 那么为了避免内存泄露, 应该将对象修饰为__block
__block Person *p = [[Person alloc] init];
p.name = @"lnj";
NSLog(@"retainCount = %lu", [p retainCount]);//1
p.pBlock = ^{
NSLog(@"name = %@", p.name);
};
NSLog(@"retainCount = %lu", [p retainCount]);//1
p.pBlock();
NSLog(@"retainCount = %lu", [p retainCount]);//1
[p release];
@implementation Person
- (void)dealloc
{
// 只要给block发送一条release消息, block中使用到的对象也会收到该消息
Block_release(_pBlock);
NSLog(@"%s", __func__);
[super dealloc];
}
@end
@property内存管理策略选择
-
非ARC
- 1> copy : 只用于NSString\block
- 2> retain : 除NSString\block以外的OC对象
- 3> assign :基本数据类型、枚举、结构体(非OC对象),当2个对象相互引用,一端用retain,一端用assign
-
ARC
- 1> copy : 只用于NSString\block
- 2> strong : 除NSString\block以外的OC对象
- 3> weak : 当2个对象相互引用,一端用strong,一端用weak
- 4> assgin : 基本数据类型、枚举、结构体(非OC对象)