一、基本原理
1、内存管理
(1)什么是内存管理
内存管理是指软件运行时对计算机内存资源的分配和使用技术,其最主要的目的是如何高效快速的分配,并且在
适当的时候释放和回收内存资源。
(2)为什么需要内存管理
移动设备的内存极其有限,每个app所能占用的内存也是有限制的,当app所占用的内存较多时,系统会发出内
存警告,这时得回收一些不需要再使用的内存空间。比如回收一些不需要使用的对象、变量等。
(3)管理范围的范围
任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效。
2、对象的基本结构
每个OC对象内部专门有4个字节的存储空间来存储引用计数器,该计数器为整数,表示对象被引用次数,即有多
少人正在使用这个OC对象。
3、引用计数工作原理
(1)什么是引用计数器
Object-C语言使用引用计数器来管理内存,也就是说每个对象都有个可以递增或递减的计数器,如果想使某个对
象继续存活,那就递增其引用计数;用完之后,就递减其计数。当计数为0的时候,就表示没人关注此对象来,于是
就可以把对象所占的内存回收。
(2)引用计数器的作用
当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1,当一个对象的引用计数器值为0
时,对象所占用内存就会被系统回收,也就是说对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不
可能被回收,除非整个程序已经退出。
(3)引用计数器的操作
retain:给对象发送一条retain消息,可以使引用计数器值+1(retian方法返回对象本身)。
release:给对象发送一天release消息,可以使引用计数器值-1.
retainCount:获得当前的引用计数器值
如下图演示了对象自创造出来之后经历一次retain及两次release操作的过程
在对象的生命周期中,其引用计数时而递增,时而递减,最终归零。
4、对象的销毁
(1)当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存会被系统回收。
(2)当一个对象被销毁时,系统会自动向对象发送一条dealloc消息。
(3)一般会重写dealloc方法,在这里释放相关资源。
(4)一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用。
(5)运行期系统会在适当的时候调用dealloc方法,绝不能直接调用该方法。
(6)一旦对象被回收了,它占用的内存就不可再用,坚持使用会导致程序崩溃(野指针错误)
5、僵尸对象、野指针和空指针的概念
(1)僵尸对象:所占用内存已经被回收的对象,该对象已经不可用
(2)野指针:当指针指向僵尸对象时即为野指针,给野指针发送消息会报错(EXC_BAD_ACCESS 坏的访问)
(3)空指针:没有指向任何东西的指针,空指针不会报错
6、Xcode的设置
(1)取消ARC
(2)开启僵尸对象监控
7、内存管理原则
(1)只要还有人在用某个对象,那么这个对象就不会被回收
(2)只要你想用这个对象,就让对象的计数器+1
(3)当你不再使用这个对象时,就让对象的计数器-1
(4)如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease,换句话说,不
是你创建的,就不用你去release或者是autorelease。
(5)只要你调用了retain,无论这个对象是如何生成的,你都要调用release
8、set方法的内存管理
如果有个OC对象类型的成员变量,就必须管理这个成员变量的内存。比如有个Book *_book
set方法的实现:
- (void)setBook:(Book *)book{
if (book != _book) {
[_book release];
_book = [book retain];
}
}
dealloc方法的实现:
- (void)dealloc {
[_book release];
[super dealloc];
}
代码示例:
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Person : NSObject
{
Book *_book;
}
- (void)setBook:(Book *)book;
- (Book *)book;
@end
#import "Person.h"
@implementation Person
- (void)setBook:(Book *)book
{
//book占用Book对象,所以Book对象计数器+1
_book = [book retain];
}
- (Book *)book
{
return _book;
}
-(void)dealloc
{
//对Person对象内存被释放时,它占有的对象计数器要-1
[_book release];
NSLog(@"Person对象释放内存");
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
@interface Book : NSObject
@end
#import "Book.h"
@implementation Book
-(void)dealloc
{
NSLog(@"Book对象被释放");
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"
int main()
{
//Person对象的计数器p=1
Person *p = [[Person alloc] init];
//Book对象的计数器b=1
Book *b = [[Book alloc] init];
//此时b+1=2
[p setBook:b];
//p不再占用Person对象了,所以p=1-1=0,计数器等于0后,该对象的内存要被释放,所以它所占有的对象的计数器也要—1,于是b=2-1=1
[p release];
//变量b也不再占用该对象了,所以b=1-1=0,对象内存释放
[b release];
return 0;
}
二、@property参数
1、控制set方法的内存管理
retain:release旧值,retain新值(用于OC对象)
assign:直接赋值,不做任何内存管理(默认,用于非OC对象类型)
copy:release旧值,copy新值(一般用于NSString*)
2、控制是否生成set方法
readwrite:同时生成set和get方法(默认)
readonly:只会生成getter方法
3、多线程管理
atomic:性能低(默认)
nonatomic:性能高
4、控制set和get方法的名称
setter:设置set方法的名称,一定要有个冒号“:”
getter:设置get方法的名称
5、set方法完善
代码示例:
#import <Foundation/Foundation.h>
@interface Car : NSObject
{
int _speed;
}
- (void)setSpeed:(int)speed;
- (int)speed;
@end
#import "Car.h"
@implementation Car
- (void)setSpeed:(int)speed
{
_speed = speed;
}
- (int)speed
{
return _speed;
}
-(void)dealloc
{
NSLog(@"速度为%d的Car对象内存释放",_speed);
//调用父类的dealloc
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Car.h"
@interface Person : NSObject
{
Car *_car;
}
- (void)setCar:(Car *)car;
- (Car *)car;
@end
#import "Person.h"
@implementation Person
- (void)setCar:(Car *)car
{
//对旧车对象进行判断,如果仍为旧车,计数器不用再—1
if (_car != car) {
//旧车对象计数器-1
[_car release];
//新车对象计数器+1
_car = [car retain];
}
}
- (Car *)car
{
return _car;
}
-(void)dealloc
{
//车对象计数器-1
[_car release];
NSLog(@"Person对象内存释放");
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Car.h"
int main()
{
//计数器p1-1
Person *p1 = [[Person alloc] init];
//c1-1
Car *c1 = [[Car alloc] init];
//c2-1
Car *c2 = [[Car alloc] init];
c1.speed = 200;
c2.speed = 300;
//c1-2
p1.car = c1;
//c1-1
[c1 release];
//c2-2,c1-0
p1.car = c2;
//p1-0,c2-1
[p1 release];
//c2-0
[c2 release];
return 0;
}
三、循环引用
1、什么是循环引用
两个对象通过彼此之间的强引用而构成一个保留环,比如A类引用B类,同时B类也引用了A类,如图所示:
他们的代码如下:
#import "B.h"
@interface A : NSObject
B *b;
@end
#import "A.h"
@interface B :NSObject
A *b
@end
这种代码编译会报错。当使用@class在两个类相互声明,就不会出现编译报错,例如将#import "B.h"改为
@class B,它会告诉编译器有这个类,但是不知道类中的具体方法。
2、@class和#import的区别
(1)#import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件
中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B
类中信息
(2)如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改
动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方
式就不会出现这种问题了
(3)在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类
3、循环retain
比如A对象retain了B对象,B对象retain了A对象,这样会导致A对象和B对象永远无法释放,解决方案:当两端互
相引用时,应该一端用retain、一端用assign
代码示例:
#import <Foundation/Foundation.h>
@class Card;
@interface Person : NSObject
//防止互相应用的对象互相retain,导致内存无法回收
@property (nonatomic,assign) Card *card; // card对象设置为assign,即计数器不会retain
@end
#import "Person.h"
#import "Card.h"
@implementation Person
-(void)dealloc
{
NSLog(@"Person对象被回收了");
//[_card release];
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
@class Person;
@interface Card : NSObject
@property (nonatomic,retain) Person *person;
@end
#import "Card.h"
#import "Person.h"
@implementation Card
- (void)dealloc
{
NSLog(@"Card内存被回收了");
[_person release];
[super dealloc];
}
@end
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Card.h"
int main()
{
//p-1
Person *p = [[Person alloc] init];
//c-1
Card *c = [[Card alloc] init];
//c-1
p.card = c;
//p-2
c.person = p;
//c-0 p-1
[c release];
//p-0
[p release];
return 0;
}
四、自动释放池(autorelease)
1、autorelease
(1)给某个对象发送一条autorelease消息时,就会将这个对象加到一个自动释放池中
(2)当自动释放池销毁时,会给池子里面的所有对象发送一条release消息
(3)调用autorelease方法时并不会改变对象的计数器,并且会返回对象本身
(4)autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的
autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用Release
2、autorelease的内部原理
NSAutoreleasePool内部包含一个可变数组(NSMutableArray),用来保存声明为autorelease的所有对象。如果一个对象声明为autorelease, 系统所做的工作就是把这个对象加入到这个数组中去。当NSAutoreleasePool自身释放的时候,会遍历数组中的所有对象,并且调用 release方法。如果对象的retainCount=0 那么系统会释放这些对象,如果retainCount>0,则会内存泄露。
3、自动释放池的创建
ios 5.0后:
@autoreleasepool
{
// ....
}
ios 5.0前:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// .....
[pool release]; // 或[pool drain];
在程序运行过程中,可以创建多个自动释放池,它们是以栈的形式存在内存中OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)
代码示例
#import <Foundation/Foundation.h>
#import "Person.h"
#import "GoodPerson.h"
int main()
{
// 自动释放池里嵌着另一个自动释放池
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
@autoreleasepool {
Person *p1 = [[[Person alloc] init] autorelease];
p1.age = 1;
} // 在这里释放p1这个对象所占据的内存
} // 在这里释放p对象的内存
// 自动释放池相当于在创建完一个对象后并不马上释放它,而是另做它用之后再释放
Person *p2 = [[Person alloc] init];
p2.age = 10;
[p2 release];
@autoreleasepool {
Person *p3 = [Person person];
p3.age = 100;
Person *p4 = [Person personWithAge:1000];
NSString *str = [NSString stringWithFormat:@"age=%d",p4.age];
NSLog(@"%@",str);
// 除了alloc、new或copy之外的方法创建的对象都被声明了autorelease,例如NSNumber。。。
NSNumber *num = [[NSNumber alloc] initWithInt:10];
[num release];
NSNumber *num1 = [NSNumber numberWithInt:100];
}
@autoreleasepool {
// 调用父类的方法,返回自己的对象
GoodPerson *g = [GoodPerson personWithAge:19];
g.money = 1000;
NSString *str1 = [NSString stringWithFormat:@"age=%d,and money=%f",g.age,g.money];
NSLog(@"%@",str1);
}
return 0;
}