------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
黑马程序员-iOS基础-Objective-C基础(六)内存管理
一、内存管理的必要性
移动设备内存空间极其有限
app占用内存较多时,系统会发出内存警告,必须回收一些不需要再使用的内存
二、内存管理的范围
任何继承了NSObject的对象
对其他基本数据类型、struct、enum等无效
三、引用计数器
1.对象的基本结构
每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象
每个OC对象内部专门有4个字节的存储空间来存储引用计数器
当引用计数器=0时会被系统回收
当对象刚刚被创建时,引用计数器=1
2.引用计数器的作用
当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1
当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。
换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
3.引用计数器的操作
给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
给对象发送一条release消息,可以使引用计数器值-1
可以给对象发送retainCount消息获得当前的引用计数器值
有一个alloc,就要有一个release
有一个retain,就要有一个release
4.对象的销毁
当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
不要直接调用dealloc方法
一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
5.要点
5.1.方法的基本使用
retain:计数器+1
release:计数器-1
retainCount:返回当前计数值
dealloc
当对象要被回收的时候就会调用
一定要调用[super dealloc],并且要放在最后面
5.2.概念
僵尸对象: 所占用内存已经被回收的对象,不能再使用
野指针: 指向僵尸对象(不可用内存)的指针,给野指针发消息会报错
EXC_BAD_ACCESS 访问一块坏的内存(已经被回收,不可用的内存)
空指针 没有指向任何东西的指针(存储的是nil,NULL,0)
给空指针发消息不会报错
5.3.retain方法会返回对象本身
练习:
//仅实现部分
#import <Foundation/Foundation.h>
#import "Person.h"
@implementation Person
//当一个Person对象被回收,就会自动调用这个方法
- (void)dealloc
{
NSLog(@"Person对象被回收");
//super dealloc一定要调用,而且放在最后面
[super dealloc];
}
@end
int main(int argc, char const *argv[])
{
// 1
Person *p = [[Person alloc] init];
NSUInteger c = [p retainCount];
NSLog(@"计数器:%ld", c);
// 2 retain方法返回的是对象本身
[p retain];
// 1
[p release];
// 0 野指针 :指向僵尸对象(不可用内存)的指针
[p release];
// -[Person setAge:] message sent to deallocated instance xxxxxx
// 给已经释放的对象发送了一条-setAge:消息
p.age = 10;
//指针p变成空指针
p = nil;//如果这一行不写会发生野指针错误,因为对象已经不可访问
//EXC_BAD_ACCESS:访问一块坏的内存(已经被回收,不可用的内存)
//野指针错误
[p release];
[nil release];//OC不存在空指针错误,不会报错,仅仅会报警告
return 0;
}
四、多对象内存管理
分析QQ堂游戏结论
只要想用这个对象,就让对象的计数器+1
当不再使用这个对象时,就让对象的计数器-1
原则
谁创建,谁release
如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease;换句话说,不是你创建的,就不用你去[auto]release
谁retain,谁release
只要你调用了retain,无论这个对象是如何生成的,你都要调用release
想使用/占用某个对象,就应该让对象的计数器+1(对象做一次retain操作)
不想使用/占用某个对象,就应该让对象的计数器-1(对象做一次release操作)
总结
有始有终,有加就有减
曾经让对象的计数器+1,就必须在最后让对象计数器-1
五、set方法内存管理
基本数据类型不需要管理内存,只需要管理对象
当对新对象进行retain操作时,应该对旧对象进行release操作,并且判断传入对象和当前对象是否一样
5.1.内存管理代码规范
1.只要调用了alloc,就必须有release(autorelease)如果对象不是用alloc生成的,就不需要release
2.set方法的代码规范
基本数据类型,直接赋值
- (void)setAge:(int)age
{
_age = age;
}
OC对象类型
- (void)setCar:(Car *)car
{
// 1.判断是不是新传进来的对象
if( car != _car )
{
//是新对象,释放老对象
[_car release];
//对新对象进行retain操作
_car = [car retain];
}
}
3.dealloc方法的代码规范
一定要调用[super dealloc],而且放到最后面
对self(当前对象)所拥有的其他对象做一次release
- (void)dealloc
{
[_car release];
NSLog(@"xxxxx");
[super dealloc];
}
5.2 @property的内存管理
@property (retain) 类名 *对象名;
retain:在生成的set方法里面,release旧值,retain新值
@property (retain) Book *book;
只影响set/get方法
-dealloc方法中完成对象释放即可
总结
1.内存管理相关的参数
retain:release旧值,retain新值(适用于OC对象类型)
assign:直接赋值(默认,适用于非OC对象类型)
copy:release旧值,copy新值
2.是否要生成set方法
readonly:只读,只会生成getter的声明和实现
readwrite:可读可写(默认),同时生成getter和setter的声明和实现
3.多线程管理
nonatomic:性能高,一般用这个
atomic:性能低(默认)
4.setter和getter方法的名称
getter = xxx
setter = xxx:
练习:
@implementation Person
- (void)setAge:(int)age
{
_age = age;
}
- (void)setCar:(Car *)car
{
// 1.判断是不是新传进来的对象
if( car != _car )
{
//是新对象,释放老对象
[_car release];
//对新对象进行retain操作
_car = [car retain];
}
}
- (void)dealloc
{
[_car release];
NSLog(@"xxxxx");
[super dealloc];
}
@end
六、@class
当有类A和类B时,如果类A需要import类B的声明,类B也需要import类A的声明时,不可以同时用#import导入
都应该使用@class,作用为告诉编译器,这是一个类,但这没有将另一个类的声明拷贝到当前类中
规范:
在声明部分用@class 类
在实现部分用#import .h头文件
总结:
1.作用:告诉编译器某个名称是一个类
2.开发中引用一个类的规范
在.h文件中用@class来声明类
在.m文件中用#import来包含类的所有东西
七、循环retain
两端循环引用的解决方案: 一端用retain, 另一端用assign
八、autorelease(非自动释放,实际是半自动释放)
8.1 建议格式
@autoreleasepool
{
Person *p = [[[Person alloc] init] autorelease];
}
8.2 总结
基本用法
autorelease会将对象放到一个自动释放池中
当自动释放池被销毁时,会对池中的所有对象做一次release操作
会返回对象本身
调用完autorelease方法后,对象的计数器不变
好处
不用再关心对象释放的时间
不用再关心什么时候调用release
注意点
占用内存较大的对象不要随便使用autorelease
当对占用内存比较小的对象时,可以考虑使用autorelease
autorelease不能精确控制释放的时间,实际编程应多使用release
错误写法
1.alloc之后调用了autorelease,又调用了release
2.连续调用多次autorelease
自动释放池@autoreleasepool
在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构存在,先进后出
当一个对象调用autorelease方法时,会将对象放到栈顶的自动释放池中
九、内存管理总结
9.1 计数器的基本操作
retain
release
retainCount
9.2 set方法的内存管理
set方法实现
dealloc方法实现,不要直接调用
9.3 @property参数
OC对象类型
非OC对象类型(int\float\enum\struct)
被retain过的属性,必须在dealloc中release属性
9.4 autorelease
系统自带方法里面没有包含alloc、new、copy说明返回对象都是autorelease过的
开发中经常会提高一些类方法快速创建一个已经autorelease过的对象,创建对象时不要用类名,一般用self
十、ARC
创建项目时勾选Use Automatic Reference Counting
基本原理:ARC的判断准则
只要没有强指针指向对象,就会释放对象
指针分2种
强指针;默认情况下所有指针都是强指针 __strong
弱指针:__weak
错误写法
__weak Person *p = [[Person alloc] init];
//生成对象就被销毁
当弱指针所指对象被销毁时,编译器自动清空弱指针
@property的strong和weak: ARC中retain换成strong
@property (nonatomic, strong) Dog *dog;
总结
1.不允许release、retain、retainCount
2.允许重写dealloc,但是不允许调用[super dealloc]
3.@property的参数
strong:成员变量是强指针,相当于原来的retain,OC对象类型
weak:成员变量是弱指针,相当于原来的assign,OC对象类型
assign:适用于非OC对象类型
4.以前的retain改成strong
Xcode的ARC转换功能:Edit-Refactor-Covert to Objective-C ARC
在ARC项目内某个文件不要使用ARC:项目-Build Phases-Compile Sources-在对应文件位置输入“-fno-objc-arc”
在非ARC项目内使某个文件使用ARC:项目-Build Phases-Compile Sources-在对应文件位置输入“-f-objc-arc”
使用注意: 两端循环引用的解决方案
ARC:一个使用strong,另一个使用weak
非ARC:一个用retain,一个用assign