一, iOS的内存管理规则
1 基本原则
移动设备的内存极其有限,每个app所能占用的内存是有限制的
当app所占用的内存较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等
管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效
QQ堂开房间原理:只要房间还有人在用,就不会解散
只要还有人在用某个对象,那么这个对象就不会被回收
只要你想用这个对象,就让对象的计数器+1
当你不再使用这个对象时,就让对象的计数器-1
- 1, 你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)
- 2, 你不想再使用(占用)某个对象,就应该让对象的计数器-1(让对象做一次release)
- 3, 谁alloc,谁release
如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
换句话说,不是你创建的,就不用你去[auto]release
- 4, 谁retain,谁release
只要你调用了retain,无论这个对象是如何生成的,你都要调用release
- 5, 总结
有始有终,有加就有减
曾经让对象的计数器+1,就必须在最后让对象计数器-1
所以,一般需要在三个地方进行release
- 在main或其他函数创建对象后.
- 在dealloc方法内
- 在set方法内,进行if判断
2 ARC
ARC是编译器(时)特性,而不是运行时特性,更不是垃圾回收器
2.1、 自动释放池
autorelease 其实并不是自动释放,而是把对release的调用延迟了
autorelease 返回的是对象本身.
// autorelease方法会返回对象本身
// 调用完autorelease方法后,对象的计数器不变
// autorelease会将对象放到一个自动释放池中
// 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
1、 autorelease的基本用法
- 给某个对象发送一条autorelease消息时,就会将这个对象加到一个* 自动释放池中
- 当自动释放池销毁时,会给池子里面的所有对象发送一条release消息
- 调用autorelease方法时并不会改变对象的计数器,
- 调用autorelease方法并且会 会返回对象本身autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用Release
2、autorelease的好处
1> 不用再关心对象释放的时间
2> 不用再关心什么时候调用release
3、autorelease的使用注意
1> 占用内存较大的对象不要随便使用 autorelease
2> 占用内存较小的对象使用autorelease,没有太大影响
4、autorelease 应用的场景
1,开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
一般可以为类添加一个快速创建对象的类方法
创建一个类方法,直接返回一个扔进释放池里面的对象
声明:
+ (id)person; 一般是和类同名的小写.
实现:
+ (id)person{
创建对象时不要直接用类名,一般用self
//因为这个self代表类 解决了子类调用父类的问题,满足子类需求 ,避免了重写
return [[[self alloc] init] autorelease];
}
调用: 创建对象
外界调用[Person person]
时,根本不用考虑在什么时候释放返回的Person对象
Person *p2 = [Person person];
以前的是: Person *p2 = [[[Person alloc] init] autorelease];
也可以扩充方法
+ (id)personWithAge:(int)age{
Person *p = [self person];
p.age = age;
return p;
}
2、规律
系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
一般来说,除了alloc、new或copy之外的方法创建的对象都被声明了autorelease
比如下面的对象都已经是autorelease的,不需要再release
NSNumber *n = [NSNumber numberWithInt:100];
NSString *s = [NSString stringWithFormat:@"jack"];
NSString *s2 = @"rose”;
2.2. ARC的修饰符
ARC提供四种修饰符,分别是strong, weak, autoreleasing, unsafe_unretained
__strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。
比如我们常用的定时器
NSTimer * timer = [NSTimer timerWith...];
相当于
NSTimer * __strong timer = [NSTimer timerWith...];
当不需要使用时,强制销毁定时器
[timer invalidate];
timer = nil;
__weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。
比如避免循环引用的弱引用声明:
__weak __typeof(self) weakSelf = self;
__autoreleasing:自动释放对象的引用,一般用于传递参数
比如一个读取数据的方法
- (void)loadData:(NSError **)error;
当你调用时会发现这样的提示
NSError * error; [dataTool loadData:(NSError *__autoreleasing *)]
这是编译器自动帮我们插入以下代码
NSError * error;
NSError * __autoreleasing tmpErr = error;
[dataTool loadData:&tmpErr];
__unsafe_unretained:为兼容iOS5以下版本的产物,可以理解成MRC下的weak,现在基本用不到,这里不作描述。
2.3. ARC的开启与关闭
设置方法:
Targets -->Build Phases -->Compile Sources
如果你的工程是开启ARC的, 那就需要对某些文件禁用ARC, (-fno-objc-arc)
如果你的工程是关闭ARC的, 那就需要对某些文件开启ARC.(-fobjc-arc)
3. @property属性的内存管理
1、控制 set方法内存管理相关的参数
- retain : release旧值,retain新值(适用于OC对象类型)
- assign : 直接赋值(默认,适用于非OC对象类型)
- copy : release旧值,copy新值(一般用于 NSString * )
2、控制 是否要生成set方法
- readwrite : 同时生成setter和getter的声明、实现(默认)
- readonly : 只会生成getter的声明、实现
3、控制 多线程管理
- nonatomic : 性能高 (一般就用这个) 不加锁
- atomic : 性能低(默认) 加锁
4、控制 setter和getter方法的名称
- setter : 决定了set方法的名称,一定要有个冒号 :
- getter : 决定了get方法的名称(一般用在BOOL类型)
// 返回BOOL类型的方法名一般以is开头
@property (getter = isRich, setter = setIsRich:) BOOL rich;
@property (nonatomic, assign, readwrite) int weight;
4. block的内存管理
Block的类型与内存管理
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。
NSGlobalBlock:类似函数,位于text段;
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。
- 无论当前环境是ARC还是MRC,只要block没有访问外部变量,block始终在全局区
- MRC情况下
1.block如果访问外部变量,block在栈里
2.不能对block使用retain,否则不能保存在堆里
3.只有使用copy,才能放到堆里 - ARC情况下
1.block如果访问外部变量,block在堆里
2.block可以使用copy和strong,并且block是一个对象
block的循环引用
如果要在block中直接使用外部强指针会发生错误,使用以下代码在block外部实现可以解决
__weak typeof(self) weakSelf = self;
但是如果在block内部使用延时操作还使用弱指针的话会取不到该弱指针,需要在block内部再将弱指针强引用一下
__strong typeof(self) strongSelf = weakSelf;
iOS中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点: 1)如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),尽量不要使用retain。
@property (nonatomic, copy) void(^block)(NSData * data);
2)block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。
__weak typeof(self) weakSelf = self;
有兴趣的读者可以深入了解:
1、block的内部实现原理是什么? 2、从内存位置来看block有几种类型?它们的内存管理方式各是怎样的? 3、对于不同类型的外部变量,block的内存管理都是怎样的?
5. 内存管理代码规范
- 1, 函数或方法 里面只要调用了
alloc
,必须有release(autorelease)
对象不是通过alloc
产生的,就不需要release
NSString *s = @"Jack";
stu.name = s;
- 2, set方法的代码规范
1> 基本数据类型:直接复制
- (void)setAge:(int)age {
_age = age;
}
2> OC对象类型
- (void)setCar:(Car *)car {
// 1.先判断是不是新传进来对象
if ( car != _car ) {
// 2.对旧对象做一次release
[_car release];
// 3.对新对象做一次retain
_car = [carretain];
}
}
- 3,
dealloc
方法的代码规范 不要直接调用dealloc
1> 一定要[super dealloc]
,而且放到最后面
2> 对self(当前)所拥有的其他对象做一次release
- (void)dealloc {
[_car release];
[super dealloc];
}
</br>
二, 经典内存泄漏
1 僵尸对象和野指针
</br>
2 循环引用
</br>
3 循环中对象占用内存大
</br>
4 无限循环
</br>
3, 项目中的注意点
- 1, 代理 nil
- 2, 通知 移除
- 3, kvo 移除
- 4, block 循环引用 __weak mrc __block
- 5, 对象的循环引用 一端 assign 一端 strong
网友评论