iOS 内存

现在项目中主要使用ARC的方式对内存进行管理,就是自动引用计数, 当这个对象的引用计数为0的时候, 该对象就会被释放,如果是alloc new copy其引用计数为+1,若给对象发送retain消息, 其引用计数+1, 若发送release消息, 其引用计数-1

问题1 NSString引用计数问题

会发现NSString的引用计数为-1, 这可以理解为NSString实际上是一个字符串常量,是没有引用计数的(或者说NSString的引用计数是一个很大的值, 对它做引用计数操作实质上是没有影响的)

问题2 赋值不会拥有某个对象

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

问题3 dealloc

由于释放对象会调用dealloc方法,因此重写dealloc方法来查看对象释放情况, 如果没有调用则存在内存泄漏

问题4 最后一次释放后, 引用计数还是1的问题

会发现获取到的引用计数为1,为什么不是0呢?   这是因为对引用计数为1的对象release时,系统知道该对象将被回收,就不会再对该对象的引用计数进行减1操作,这样可以增加对象回收的效率。   另外,对已释放的对象发送消息是不可取的,因为对象的内存已被回收,如果发送消息时,该内存已经被其他对象使用了,得到的结果是无法确定的,甚至会造成崩溃。

自动释放

现在已经明确了, 当不再使用一个对象是应该将其释放,但是某些情况下,很难理清一个对象什么时候不再使用, 怎么办?

所以Objc提供autorelease来解决这个问题,当給一个对象发送autorelease消息时,方法会在未来某个时间給这个对象发送release消息将其释放,在这个时间段内,对象还是可以使用的。

autorelease原理

原理就是对象接收到autorelease消息时,它会被添加到了当前的自动释放池中,当自动释放池被销毁时,会給池里所有的对象发送release消息。

这里就引出了自动释放池这个概念,什么是自动释放池呢? 顾名思义,就是一个池,这个池可以容纳对象,而且可以自动释放,这就大大增加了我们处理对象的灵活性。

如何创建自动释放池

Objc提供两种方法创建自动释放池

  1. 使用NSAutoreleasePool创建
 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc]init];
 //这里写代码
 [pool release];
  1. 使用@autoreleasepool创建
 @autoreleasepool {
 //这里写代码
 }
自动释放池什么时候创建?

app使用过程中,会定期自动生成和销毁自动释放池,一般是在程序事件处理之前创建,当然我们也可以自行创建自动释放池,来达到我们一些特定的目的。

自动释放池什么时候销毁?

自动释放池的销毁时间是确定的,一般是在程序事件处理之后释放,或者由我们自己手动释放。
运行循环结束前会释放自动释放池,还有就是池子满了,也会销毁

案例1
//创建一个自动释放池 
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; //模拟:宠物中心派出小狗 
Dog * dog = [[Dog alloc]init]; //模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1 
[dog retain]; 
NSLog(@"小狗的引用计数为 %ld",dog.retainCount);
//模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1 
[dog retain];
NSLog(@"小狗的引用计数为 %ld",dog.retainCount); //模拟:xiaoming确定不想和小狗玩耍了,需要将其引用计数减1 
[dog release]; 
NSLog(@"小狗的引用计数为 %ld",dog.retainCount); //模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放 
[dog autorelease];
NSLog(@"小狗的引用计数为 %ld",dog.retainCount); //没人需要和小狗玩耍了,将其引用计数减1 
[dog release]; NSLog(@"释放池子"); 
[pool release];

案例2
//创建一个自动释放池 
@autoreleasepool { 
    //模拟:宠物中心派出小狗 
    Dog * dog = [[Dog alloc]init];       //模拟:xiaoming需要和小狗玩耍,需要将其引用计数加1 
    [dog retain]; 
    NSLog(@"小狗的引用计数为 %ld",dog.retainCount); //模拟:xiaohong需要和小狗玩耍,需要将其引用计数加1 
    [dog retain]; 
    NSLog(@"小狗的引用计数为 %ld",dog.retainCount); //模拟:xiaoming确定不想和小狗玩耍了,需要将其引用计数减1 
    [dog release]; 
    NSLog(@"小狗的引用计数为 %ld",dog.retainCount); //模拟:xiaohong不确定何时不想和小狗玩耍了,将其设置为自动释放 
    [dog autorelease]; 
    NSLog(@"小狗的引用计数为 %ld",dog.retainCount); //没人需要和小狗玩耍了,将其引用计数减1 
    [dog release]; 
    NSLog(@"释放池子"); 
}

自动释放池注意点
  • 自动释放池实质上只是在释放的时候給池中所有对象对象发送release消息,不保证对象一定会销毁,如果自动释放池向对象发送release消息后对象的引用计数仍大于1,对象就无法销毁。

  • 自动释放池中的对象会集中同一时间释放,如果操作需要生成的对象较多占用内存空间大,可以使用多个释放池来进行优化。比如在一个循环中需要创建大量的临时变量,可以创建内部的池子来降低内存占用峰值。1亿次打印循环

  • autorelease不会改变对象的引用计数

ARC的修饰符__strong, __weak, __autoreleasing, __unsafe_unretained
  • __strong:强引用,持有所指向对象的所有权,无修饰符情况下的默认值。如需强制释放,可置nil。
  • __weak:弱引用,不持有所指向对象的所有权,引用指向的对象内存被回收之后,引用本身会置nil,避免野指针。
  • __autoreleasing:自动释放对象的引用,一般用于传递参数
  • __unsafe_unretained:为兼容iOS5以下版本的产物,可以理解成MRC下的weak,现在基本用不到
属性的内存管理
  • assign:直接赋值 assign一般用来修饰基本数据类型
  • retain:release旧值,再retain新值(引用计数+1)
      retain和strong一样,都用来修饰ObjC对象。
      使用set方法赋值时,实质上是会先保留新值,再释放旧值,再设置新值,避免新旧值 一样时导致对象被释放的的问题。
 - (void)setCount:(NSObject *)count {
     [count retain];
     [_count release];
     _count = count;
 }
  • copy:release旧值,再copy新值(拷贝内容)   一般用来修饰String、Dict、Array等需要保护其封装性的对象,尤其是在其内容可变的情况下,因此会拷贝(深拷贝)一份内容給属性使用,避免可能造成的对源内容进行改动。   使用set方法赋值时,实质上是会先拷贝新值,再释放旧值,再设置新值。   实际上,遵守NSCopying的对象都可以使用copy,当然,如果你确定是要共用同一份可变内容,你也可以使用strong或retain。

  • weak:ARC新引入修饰词,可代替assign,比assign多增加一个特性(置nil,见上文)。
      weak和strong一样用来修饰ObjC对象。
      使用set方法赋值时,实质上不保留新值,也不释放旧值,只设置新值。

比如常用的代理的声明

  • strong:ARC新引入修饰词,可代替retain
block内存管理

iOS中使用block必须自己管理内存,错误的内存管理将导致循环引用等内存泄漏问题,这里主要说明在ARC下block声明和使用的时候需要注意的两点:   1)如果你使用@property去声明一个block的时候,一般使用copy来进行修饰(当然也可以不写,编译器自动进行copy操作),
2)block会对内部使用的对象进行强引用,因此在使用的时候应该确定不会引起循环引用,当然保险的做法就是添加弱引用标记。
不要使用retain。

常见ARC内存泄漏
  1. 僵尸对象和野指针
僵尸对象:内存已经被回收的对象。
野指针:指向僵尸对象的指针,向野指针发送消息会导致崩溃。

野指针错误形式在Xcode中通常表现为:Thread EXC_BAD_ACCESS,因为你访问了一块已经不属于你的内存。   例子代码:(没有出现错误的筒子多运行几遍,因为获取野指针指向的结果是不确定的)

解决办法: 对象已经被释放后, 应将其指针置Wie空指针

  1. 循环引用

  2. 循环中对象占用内存大
    这个问题常见于循环次数较大,循环体生成的对象占用内存较大的情景。
      例子代码:我需要10000个演员来打仗

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

 for (int i = 0; i < 10000; i ++) {
     @autoreleasepool {
         Person * soldier = [[Person alloc]init];
         [soldier fight];
     }
 }
  1. 无限循环
    无论你出于什么原因,当你启动了一个无限循环的时候,ARC会默认该方法用不会执行完毕,方法里面的对象就永不释放,内存无限上涨,导致内存泄漏。

内存管理

你可能感兴趣的:(iOS 内存)