IOS内存管理

堆与栈

1.栈区(stack):由编译器自动分配释放,函数的参数值,局部变量等值。

2.堆区(heap):一般由开发人员分配释放,若不释放,则可能会引起内存泄漏。

NSString* string = @"sdfsdf";//常量string->栈

NSInteger index = 0; //index->栈

NSMutableString* mString = [[NSMutableString alloc] initWithString:@"sdfsdf"];//mString->堆

IOS内存管理

Objective-C提供了两种种内存管理方式:manual reference countingMRC,手动引用计数器),automatic reference countingARC,自动引用计数)。ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存;

引用计数器

ObjC采用引用计数(reference counting)的技术来进行管理:

  1. 每个对象都有一个关联的整数,称为引用计数器
  2. 当代码需要使用该对象时,则将对象的引用计数加1
  3. 当代码结束使用该对象时,则将对象的引用计数减1
  4. 当引用计数的值变为0时,此时对象将被释放。

与之对应的消息发送方法如下:

  1. 当对象被创建(alloc、new或copy等方法)时,其引用计数初始值为1
  2. 给对象发送retain消息,其引用计数加
  3. 给对象发送release消息,其引用计数减1
  4. 当对象引用计数归0时,ObjC給对象发送dealloc消息销毁对象

下面通过一个简单的例子来说明:

新建RetainCountObject类,重写其创建和销毁的方法

@implementation RetainCountObject

- (instancetype)init {

    self = [super init];

    if (self) {

        NSLog(@"初始引用计数为 %ld",self.retainCount);

    };

    return self;

}

- (void)dealloc {

    NSLog(@"对象被释放");

    NSLog(@"release后的引用计数为 %ld", self.retainCount);

    [super dealloc];

}

@end

在ViewDidLoad方法中创建RetainCountObject对象,給object发送消息     

RetainCountObject * object = [[RetainCountObject alloc]init];

[object retain];

NSLog(@"object引用计数为 %ld", object.retainCount);

[object release];

NSLog(@"objec引用计数为 %ld", object.retainCount);

[object release];      //将指针置nil,否则变为野指针

object = nil;

MRC_Project[40145:1469636] 初始引用计数为 1

MRC_Project[40145:1469636] object引用计数为 2

MRC_Project[40145:1469636] objec引用计数为 1

MRC_Project[40145:1469636] 对象被释放

注意一些特殊的情况:

NSString引用计数问题

如果我们尝试查看一个string的引用计数

NSString * str = @" hello";   NSLog(@"hello guys :%ld", str.retainCount); 

MRC_Project[40189:1472607] hello :-1

NSString实际上是一个字符串常量,由栈管理,是没有引用计数的。

赋值不会拥有某个对象

NSString* title = object.title;

这里仅仅是指针赋值操作,并不会增加name的引用计数,需要持有对象必须要发送retain消息。

Dealloc

由于释放对象是会调用dealloc方法,因此重写dealloc方法来查看对象释放的情况,如果没有调用则会造成内存泄露。在上面的例子中我们通过重写dealloc让小狗被释放的时候打印日志来告诉我们已经完成释放。

在上面例子中,如果我们增加这样一个操作

//最后release object时

[object release];     

NSLog(@"release后的引用计数为 %ld", self.retainCount);

[super dealloc];


MRC_Project[40314:1477373] release后的引用计数为 1

会发现获取到的引用计数为1,为什么不是0呢?

这是因为对引用计数为1的对象release时,系统知道该对象将被回收,就不会再对该对象的引用计数进行减1操作,这样可以增加对象回收的效率。

自动释放池

Autoreleasepool的原理

自动释放池,系统有一个现成的自动内存管理池,他会随着每一个mainRunloop的结束而释放其中的对像;自动释放池也可以手动创建,他可以让pool中的对象在执行完代码后马上被释放,可以起到优化内存,防止内存溢出的效果(如视频针图片的切换时、创建大量临时对象时等)。

autorelease:自动释放,使对象在超出指定的生存范围时能够自动并正确地释放 release 即是立即释放)。

自动释放池的创建

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc]init];

// do something

id obj = [[NSMutableArray alloc] init];

[obj autorelease];

[pool release];

// [pool drain];//GC(垃圾回收机制)环境没影响

 对于所有调用过autorelease实例方法的对象,在pool release时,将调用release释放对象

@autoreleasepool {

// do something

}

MRC方法放回对象时需要autorelease

- (NSArray *)getArray{

    NSArray* array = [NSArray array];

    return array;

    /*

    NSArray* array = [[[NSArray alloc] init] autorelease];

    return array;

     */

}

自动释放池的触发

- (void)autoRelease_Test {

    @autoreleasepool {

        TestModel *model = [[TestModel alloc] init];

        [model autorelease];

        //model can dongSomething you want

        NSLog(@"自动释放:end");

    }

}

MRC_Project[2678:287011] 自动释放:end

MRC_Project[2678:287011] TestModel dealloc

可以看到,当自动释放调用后,model对象才被释放,因此在池子释放之前,model可以正常调用

IOS内存管理规则

基本原

  1. 当你通过new、alloc或copy方法创建一个对象时,它的引用计数为1,当不再使用该对象时,应该向对象发送release或者autorelease消息释放对象。
  2. 当你通过其他方法获得一个对象时,如果对象引用计数为1且被设置为autorelease,则不需要执行任何释放对象的操作;
  3. 如果你打算取得对象所有权,就需要保留对象并在操作完成之后释放,且必须保证retain和release的次数对等。

ARC的修饰变量

strong,  weak,  autoreleasing,  unsafe_unretained

__strong //强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil

比如我们常用的定时器:

NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:nil userInfo:nil repeats:NO];

相当于:

NSTimer* __strong timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:nil userInfo:nil repeats:NO];

当不需要使用时,强制销毁定时器:

[timer invalidate];

timer = nil;

__weak //弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。

比如避免循环引用的弱引用声明:

__weak typeof(self) weakSelf = self;

__autoreleasing //自动释放对象的引用,一般用于传递参数

比如一个读取数据的方法:

- (void)doSomething:(NSError **)error

当你调用时会发现这样的提示:

[self doSomething: (NSError *__autoreleasing *)]

这是编译器自动帮我们插入以下代码:

NSError* __autoreleasing error = nil;

[self doSomething:&error];

__unsafe_unretained //弱引用,内存被释放后有僵尸对象,会产生野指针,不建议使用

arc及mrc的属性

@property (nonatomic, assign) int value; //简单的赋值

@property (nonatomic, retain) NSArray* array; //引用计数器加一,指针复制

@property (nonatomic, copy) NSString* string; //生成新的内存区域,内容复制

@property (nonatomic, strong) NSNumber* number;//强引用,当所有指向同一块内存的强指针都赋空时,内存将被释放

@property (nonatomic, weak) NSData* data; //弱引用, 当所有指向同一块内存的强指针都赋空时,弱引用失效,为nil

@property (nonatomic, unsafe_unretained) NSData* data1; //不安全性弱引用,当所有指向同一块内存的强指针都赋空时,该对象将成为野指针,再次调用会导致程序崩溃

- (NSString *)string{

    return _string;

}

- (void)setString:(NSString*)string{

    _string = string;

    //weak,strong,assign 的set方法

}

- (void)setString:(NSString*)string{

    if(_string != string) {

        [_string release];

        _string = [string retain];

    }

    //retain 的set方法

}

- (void)setString:(NSString*)string{

    if(_string != string) {

        [_string release];

        _string = [string copy];

    }

    //copy 的set方法

}

//释放属性对象

- (void)dealloc {

    self.string = nil;

    [super dealloc];

}

block内存管理

block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈(stack)上,而不是在堆(heap)上。

@property(copy, nonatomic) void(^block)(void);

mrc copy会把block从栈上移动到堆上。

@property(strong, nonatomic) void(^block)(void);

arc即时由强引用strong将其从栈复制到堆

block 的内存管理:

IOS内存管理_第1张图片

当程序运行到这里时,stack 空间中有 shared 变量和 captured 变量。

这里可以看出,__block 变量开始是处于stack上的。

IOS内存管理_第2张图片

当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1

这里可以看出,block 类型的变量开始时也是处在stack上的。

IOS内存管理_第3张图片

当程序运行到这里时,stack 空间中有 shared 变量,captured 变量和block1

这里值得注意的就是当我们直接修改stack 上的captured变量时,block1中的captured变量仍然是原来的数值10。事实上,从const 我们就可以看出,block1中的captured变量是不能被修改的而且是从stack原有变量的一个const 拷贝。在block1中访问的captured变量是const拷贝的,也就是说block1captured = 10,而不是原有的stack上的值 20。当然,在block1中,我们也不能修改captured变量。

Copy block:

block在一开始是处在stack上的,这是为了考虑到效率的原因,但是,有时候是需要block的生命周期长于一开始的stack,这时,我们就通过copy block 来将block复制到heap

IOS内存管理_第4张图片

当程序执行完 block2 = [block1 copy];时,__block 类型变量shared,被复制到了heap中,很显然,shared变量需要被blockblock2共享(当然还有stack也要共享),而block2被移动到heap中,很可能生命周期会长于stack,所以,shared也被复制到了heap中。而block2中的captured 也被复制到了heap中。

IOS内存管理_第5张图片

当程序执行完 block3 = [block2 copy];, 我们看到的是,block2 block3 其实指向的是同一片内存空间。事实上,block的数据结构中,保存了引用计数,而对于copyheap中的block copy时,行为同普通对象retain一样,会使引用计数+1。那么如果我们对[block retain]会如何呢? 实际上什么都没有发生,至少在现在的runtime版本下。因为retain中,不仅有引用计数+1在,而且retain的返回值,必须同返回调用对象的地址一样,而block的地址是可能变化的(stack or heap),所以,这里retain的行为几乎是被忽略掉的。

heap中的block变量先于stack被销毁时,如调用 [block2 release]; [block3 release];heap中的block2block3 由于引用计数为0 而被销毁,而 __block 变量shared则还在heap中,因为stack还要使用,block1 也要使用。

IOS内存管理_第6张图片

heap中的block变量晚于stack时,显然,stack 被清除,function中也啥都没了。

IOS内存管理_第7张图片

最后,当block2 block3 都被release之后。则恢复到最初状态

IOS内存管理_第8张图片

从mrc到arc的转变

项目 -> Build Phases -> Compile Sources 找到要修改的文件

如果是ARC工程添加MRC文件则输入:-fno-objc-arc

如果是MRC工程添加ARC文件则输入:-fobjc-arc

 

引用方式:

copy:拷贝,复制一个对象并创建strong关联,引用计数为1 ,原来对象计数不变。

assign:赋值,不涉及引用计数的变化,弱引用。ARC中对象不使用assign,但原始类型(BOOLintfloat)仍然可以使用。

retain:持有,对原对象引用计数加1,强引用。ARC中使用strong

weak:赋值(ARC),比assign多了一个功能,对象释放后把指针置为nil,避免了野指针。

strong:持有(ARC),等同于retain

 

在你打开ARC时,你是不能使用retainreleaseautorelease 操作的,原先需要手动添加的用来处理内存管理的引用计数的代码可以自动地由编译器完成了,但是你需要在对象属性上使用weak strong, 其中strong就相当于retain属性,而weak相当于assign,基础类型只需声明非原子锁即可。

 

以下代码在ARC之前是可能会行不通的,因为在手动内存管理中,NSArray中移除一个对象时,这个对象会发送一条release消息,可能会被立即释放。随后NSLog()打印该对象就会导致应用崩溃

id obj = [array objectAtIndex:0];  

[array removeObjectAtIndex:0];  

NSLog(@"%@", obj);  

ARC中这段代码是完全合法的,因为obj变量是一个strong指针,它成为了对象的拥有者,NSArray中移除该对象也不会导致对象被释

经典内存泄漏

僵尸对象和野指针

僵尸对象:内存已经被回收的对象。

野指针:指向僵尸对象的指针,向野指针发送消息会导致崩溃。

EXC_BAD_ACCESS

循环引用

arc中默认的对象声明都是strong性质的,在两个或两个以上的类相互引用时,会导致循环引用,其中一方需要用weak修饰,才不会造成retainCycle,如:delegate 属性用weak声明;mrc中即用assign修饰

block中引用block所属的类、实例变量或类的属性也会导致循环引用

self.block = ^{

        [self doSomething];

 };

block是会对内部的对象进行一次retain。也就是说,self会被retain一次。当self释放的时候,需要block释放后才会对self进行释放,但是block的释放又需要等selfdealloc中才会释放。如此一来变形成了循环引用,导致内存泄露。

arc中用__weak修饰selfmrc中用__block修饰,如下代码:

__weak ViewController* weakSelf = self;//arc

//__block ViewController* weakSelf = self;//mrc

self.block = ^{

   [weakSelf doSomething];

};

环中对象占用内存大

这个问题常见于循环次数较大,循环体生成的对象占用内存较大的情景。
例子代码:

for (int i = 0; i < 10000; i ++) {
  Person * soldier = [[Person alloc]init]; 
  [soldier fight];     
}

 

该循环内产生大量的临时对象,直至循环结束才释放,可能导致内存泄漏,解决方法和上文中提到的自动释放池常见问题类似:在循环中创建自己的autoReleasePool,及时释放占用内存大的临时变量,减少内存占用峰值。

for (int i = 0; i < 10000; i ++) {
  @autoreleasepool {
    Person* soldier = [[Person alloc]init];
    [soldier fight];          
  }
}

然而有时候autoReleasePool也不是万能的:

for (int i = 0; i < 2000; i ++) {
  CGSize size = [UIImage imageNamed:[NSString stringWithFormat:@"%d.jpg",i]].size;          
}

 

imageNamed方法加载图片占用Cache的内存,autoReleasePool也不能释放。

for (int i = 0; i < 2000; i ++) {
  @autoreleasepool {
    CGSize size = [UIImage imageWithContentsOfFile:filePath].size;              
   }
}

无限循环

例如递归死循环,forwhile无限循环,NSTimer无限调用

系统内存警告

- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    self.view = nil;

    self.data = nil;

}

 

ARC & MRC demo:

https://github.com/zhengmiaokai/ARC_Project.git

https://github.com/zhengmiaokai/MRC_Project.git

你可能感兴趣的:(Objective-C)