1.为什么要内存管理?
2.OC是如何管理内存的?
#import <Foundation/Foundation.h>
@interface Person : NSObject
{
int _age ;
}
@property int age;
@end
#import "Person.h"
@implementation Person
// 当一个Person对象被回收的时候,就会自动调用这个方法
- (void)dealloc
{
NSLog(@"Person对象被回收");
// super的dealloc一定要调用而且放在最后面
// EXD_BAD_ACCESS:访问了一块坏的内存(已被回收和释放的内存)
// 野指针
[super dealloc];
}
@end
计数器操作代码:
int main()
{
/* 创建Person对象 */
Person *p = [[Person alloc] init];
/* 获取对像的计数器初始值 */
NSInteger c = [p retainCount];
NSLog(@"计数器初始值 %ld",c); //值为1
// 2.调用retain方法,计数器加1,retain方法返回的是对象本身
[p retain];
c = [p retainCount];
NSLog(@"retain之后计数器值 %ld",c); //值为2
// 减为0才会被回收
// 调用alloc 调用return 就必须有release
[p release]; // 减一操作
c = [p retainCount];
NSLog(@"release后计数器值 %ld",c); //值为1
[p release]; // 减一操作之后值为0,系统回收内存,执行dealloc方法,挂掉后面也不能在加1
// 给已经释放的对象发送了一条setAge消息: 报错
//p.age = 20;
//防止野指针
p = nil;
// 不能多次释放,产生野指针,OC里面指向僵尸对象(不可用内存)
// EXD_BAD_ACCESS:访问了一块坏的内存(已被回收和释放的内存)
// 野指针
[p release];//给空指针发消息不会报错,OC不存在空指针错误
return 0;
}
运行结果:
2015-03-23 21:02:45.239 内存管理-引用计数器的操作[1521:161738]计数器初始值 1
2015-03-23 21:02:45.241 内存管理-引用计数器的操作[1521:161738] retain之后计数器值 2
2015-03-23 21:02:45.241 内存管理-引用计数器的操作[1521:161738] release后计数器值 1
2015-03-23 21:02:45.241 内存管理-引用计数器的操作[1521:161738] Person对象被回收
-(void)setAge:(int)age
{
_age = age;
}
- (void)setCar:(Car *)car
{
// 1.先判断是否是新传来的对象
if (_car != car)
{
// 2.旧对象做一次release
[_car release]; // 第一次的话_car为空,
//对空对象release不会报错
// 3.新对象做一次retain
_car = [car retain];
}
}
// 1.一定要[super dealloc];而且放到最后面
- (void)dealloc;
{
[self->_car release];
//当你挂掉时必须让你拥有的对象的计数器减一
NSLog(@"%d 岁的人对象被回收",self->_age);
[super dealloc];
}
4. 使用@property自动创建对象的set方法时如何处理内存管理?
1>> 内存管理相关
retain : release 旧值retain新值(适用于OC对象新值)
assign : 直接赋值(默认:适用于非Oc对象类型)
copy : release 旧值copy新值
例: @property (retain) NSString *name
2>> 是否要生成set方法
有种情况是,某个变量是只读的不提供设置值的方法;
readwrite: 同时生成set get方法(默认)
readonly:只生成get声明、实现
例: @property (readwrite,assign) int age;
3>> 多线程管理
nonatomic: 性能高(一般就用这个)
atomic:性能低(默认)
例: @property (nonatomic,assign) int age;
4>>set 和get方法名称
setter :决定set方法名称,一定要有冒号, 默认是setAge:
getter:决定get方法名称(一般用在BOOL类型)
常用于改变返回值BOOL类型的get方法的名称,一般以is开头
例:@property (getter = abc,setter = setAbc: ) int weight;
get方法叫做 abc
返回值BOOL类型的方法名称,一般以is开头
@property (getter = isRich) BOOL rich; // 这种在开发中常见
1. autorelease基本用法
1》会将对象放到一个自动释放池中
2》挡自动释放池销毁时,会对池子里面的所有对象做一次release操作
3》会返回对象本身
4》调用完release后对象计数器并不会改变,只有到池子结束时才会release
5》池子存储在栈中
2. autorelease 好处
1》不用在关心对象释放的时间
2》不在关心什么时候调用release
3. autorelease
1》占用内存较大的对象不要随便使用Release
2》占用内存较小的对象使用autorelease,没有太大影响
4.错误写法
1》alloc之后调用了autorelease,又调用了release
@autoreleasepool
{
// 调用两次autorelease,就会调用两次release出现野指针错误
Person *p = [[[[Person alloc] init] autorelease]; autorelease];
[p release];
}
2》连续调用对次release
Person *p = [[[[Person alloc] init] autorelease] autorelease];
5. 自动释放池
1》在IOS程序运行过程中会创建无数的池子。这些池子都已栈的方式存在先进后出
2》当一个对像调用autorelease方法时,会将这个对象放到栈顶释放池
autorelease使用示例:#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,assign) int age;
@end
Person类的实现;
#import "Person.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person 对象被回收");
[super dealloc];
}
@end
autorelease使用:
int main()
{
// autorelease 返回对象本身
// 调用完release后对象计数器并不会改变,只有到池子结束时才会release
// sutorelease 作用,会将对象放到自动释放池中,当释放池被销毁时会将
// 池子中所有的对象做一次release操作,池子在哪
@autoreleasepool
{// { 代表释放池开始
Person *p =[[[Person alloc] init] autorelease];
p.age = 10;
// 可创建n多个自动释放池
@autoreleasepool
{
Person *p =[[[Person alloc] init] autorelease];
p.age = 20;
}
//[p release];
}// {代表释放池销毁
return 0;
}
运行结果:
2015-03-23 21:52:55.539 autorelease[1627:174759] Person对象被回收
2015-03-23 21:52:55.540 autorelease[1627:174759] Person对象被回收
可见在地址池结束时确实释放了我们创建的对象。四.循环引用的内管管理
#import <Foundation/Foundation.h>
// 不用import
// @class 仅仅告诉编译器card是一个类
@class Card;
@interface Person : NSObject
@property (nonatomic,retain) Card * card;
@end
3.关于@class
1》使用 @class 类名; 就可以引用一个类,说明一下它是一个类
2》 @class 和#import的区别
#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;
@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息.
如果有n多个文件都#import了同一个头文件,那么如果最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率肯定是很慢的,而使用@class方式就不会出现这种问题
在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类
Person类的实现(使用了#import)
#import "Person.h"
// 在.m文件中用到才包含
#import "Card.h"
@implementation Person
- (void)dealloc
{
NSLog(@"Person 对象被回收");
// 回收之前将拥有的对象计数器减一
[_card release];
[super dealloc];
}
@end
Card类的声明
#import <Foundation/Foundation.h>
// 仅在声明时告诉编译器这是一个类
@class Person;
@interface Card : NSObject
/* 生分证上的 人属性*/
// 由于Person类是对象所以使用retain参数
@property (nonatomic,retain) Person *person;
@end
Card类的实现
- (void)dealloc
{
NSLog(@"Card对象被回收");
// 回收之前将拥有的对象计数器减一
[_person release];
[super dealloc];
}
@end
会引起什么问题?
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Card.h"
int main()
{
// 创建Person类的对象
Person *p = [[Person alloc] init];
// 创建Card 类的对象
Card *c = [[Card alloc] init];
// 使Person对象拥有Card对象
p.card = c;
c.person = p;
// 使Card对象拥有Person对象
[c release];
[p release];
return 0;
}
运行结果:
上面的代码看似没有什么问题,但是没有调用person对像和Card对象的dealloc方法,说明两个对象都没有被释放?
分析原因:
p、c创建时计数器都为1,执行完p.card = c,之后c的计数器为2,同理c.peron= p之后p的计数器也为2,继续执行后面的[c release],c的计数器为1,不为空就不会执行card的dealloc方法,就不会使他拥有的person对象计数器减一,那么p的计数器还是2,之后执行[p relase]p计数器变为1,也不会执行person的dealloc,这样程序运行完毕,哪个对象都没有释放。
3.解决方案
当两端互相引用时,应该一端用retain、一端用assign
先修改card端修改之后如下:
#import <Foundation/Foundation.h>
// 仅在声明时告诉编译器这是一个类
@class Person;
@interface Card : NSObject
/* 生分证上的 人属性*/
// 由于Person类是对象所以使用retain参数
//@property (nonatomic,retain) Person *person;
// 修改之后的代码
@property (nonatomic,assign) Person *person;
@end
#import "Card.h"
#import "Person.h"
@implementation Card
- (void)dealloc
{
NSLog(@"Card对象被回收");
// 回收之前将拥有的对象计数器减一
// [_person release];
[super dealloc];
}
@end
main函数不变运行结果:
2015-03-23 22:38:42.620 循环引用[1729:186447] Person对象被回收
2015-03-23 22:38:42.621 循环引用[1729:186447] Card对象被回收