OC语法之-内存管理

基本原理

  1. 管理范围:任何继承了NSObject的对象,对其他基本数据类型(int、char、float、double、struct、enum等)无效
  2. 对象的基本结构
    1. 每个OC对象都有自己的引用计数器,是一个整数,表示“对象被引用的次数”,即有多少人正在使用这个OC对象
    2. 每个OC对象内部专门有4个字节的存储空间来存储引用计数器
  3. 引用计数器的作用:
    1. 当使用alloc、new或者copy创建一个新对象时,新对象的引用计数器默认就是1
    2. 当一个对象的引用计数器值为0时,对象占用的内存就会被系统回收。换句话说,如果对象的计数器不为0,那么在整个程序运行过程,它占用的内存就不可能被回收,除非整个程序已经退出
  4. 操作引用计数器
    1. 给对象发送一条retain消息,可以使引用计数器值+1(retain方法返回对象本身)
    2. 给对象发送一条release消息,可以使引用计数器值-1(没有返回值)
    3. 可以给对象发送retainCount消息获得当前的引用计数器值
  5. 对象的销毁
    1. 当一个对象的引用计数器值为0时,那么它将被销毁,其占用的内存被系统回收
    2. 当一个对象被销毁时,系统会自动向对象发送一条dealloc消息
    3. 一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言
    4. 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用
    5. 不要直接调用dealloc方法
    6. 一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针错误)
  6. 概念
    1. 僵尸对象 :所占用内存已经被回收的对象,僵尸对象不能再使用
    2. 野指针 :指向僵尸对象(不可用内存)的指针,给野指针发送消息会报错(EXC_BAD_ACCESS)
    3. 空指针 :没有指向任何东西的指针(存储的东西是nil、NULL、0),给空指针发送消息不会报错

代码举例:

////Person.h文件
#import <Foundation/Foundation.h>

@interface Person : NSObject

@property int age;

@end

//Person.m文件
#import "Person.h"

@implementation Person

// 当一个Person对象被回收的时候,就会自动调用这个方法
- (void)dealloc
{
    NSLog(@"Person对象被回收");
    
    // super的dealloc一定要调用,而且放在最后面
    [super dealloc];
}

@end

//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"

int main()
{
    // 1
    Person *p = [[Person alloc] init];
    
    NSUInteger c = [p retainCount];
    
    NSLog(@"计数器:%ld", c);
    
    // 2  retain方法返回的是对象本身
    [p retain];
    
    // 1
    [p release];
    
    // 0 野指针:指向僵尸对象(不可用内存)的指针
    [p release];
   
    // 给野指针发送消息,会报错
    // 野指针不能复活!!!
    [p retain];
    // -[Person setAge:]: message sent to deallocated instance 0x100109a10
    // 给已经释放的对象发送了一条-setAge:消息:
    p.age = 10;
    
    //安全操作:指针p变成空指针,给空指针发送消息不报错
    //p = nil;
    
    // EXC_BAD_ACCESS : 访问了一块坏的内存(已经被回收、已经不可用的内存
    // 野指针错误
    // OC不存在空指针错误,给空指针发送消息,不报错
    [p release];
    [p release];
    [p release];
    [p release];
    //不会报错
    [nil release];
    
    return 0;
}

内存管理原则

当一个对象进行管理的时候比较简单,但是多个对象联系起来的时候,就比较麻烦了.那么他们只要遵守以下原则,就仍然很好管理.

  1. 只要还有人在用某个对象,那么这个对象就不会被回收
  2. 你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作)
  3. 你不想再使用(占用)(当前对象死掉或者换掉占有)某个对象,就应该让对象的计数器-1(让对象做一次release)
  4. 谁创建,谁release
    1. 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
    2. 换句话说,不是你创建的,就不用你去[auto]release
  5. 谁retain,谁release
    1. 只要你调用了retain,无论这个对象是如何生成的,你都要调用release
  6. 总结
    1. 有始有终,有加就有减
    2. 曾经让对象的计数器+1,就必须在最后让对象计数器-1

代码举例:

//Book.h
#import <Foundation/Foundation.h>

@interface Book : NSObject
{
    int _price;
}
- (void)setPrice:(int)price;
- (int)price;
@end
//Book.m
#import "Book.h"
@implementation Book

- (void)setPrice:(int)price
{
    _price = price;
}

- (int)price
{
    return _price;
}
- (void)dealloc
{
    NSLog(@"Book对象被回收");
    [super dealloc];
}
@end

//Person.h
#import <Foundation/Foundation.h>
#import "Book.h"

@interface Person : NSObject
{
    Book *_book;
}
- (void)setBook:(Book *)book;
- (Book *)book;
@end
//Person.m
#import "Person.h"

@implementation Person
- (void)setBook:(Book *)book
{   //拥有做一次retain
    _book = [book retain];
}

- (Book *)book
{
    return _book;
}

- (void)dealloc
{
    //死前做一次release
    [_book release];
    NSLog(@"Person对象被回收");
    [super dealloc];
}
@end


//main.m
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"

int main()
{
    // b-1
    Book *b = [[Book alloc] init];
    // p-1
    Person *p1 = [[Person alloc] init];
    
    //p1想占用b这本书那就使b做retain一次(封装在set方法中),p1死之前要把b做relase(封装在delloc中)一次
    // b-2
    [p1 setBook:b];
    
    // p-0
    // b-1
    [p1 release];
    p1 = nil;
    
    // b-0
    [b release];
    b = nil;
    return 0;
}

set方法的内存管理

从上面的例子我们可以看到:

  1. 当A对象(Person)想拥有B(Book)对象时,在A对象的set方法中对B对象进行一次retain操作;
  2. 当A对象不想拥有B对象时,在A对象的delloc中对B对象做一次Release操作
  3. 疑问:上面做的并不严谨,因为A对象不再拥有B对象,有两种情况:
    1. A对象计数器为0,被销毁时
    2. A对象替换掉旧的B对象,从新拥有一个新的B对象
  4. 那么是第2种情况该怎么办呢?很显然要对旧的B也要进行一次release,那么在哪处理呢?
  5. set方法的代码规范(A可能会替换掉B)
    1. 基本数据类型:直接复制

       - (void)setAge:(int)age
        { 
           _age = age;
        }
      
    2. OC对象类型

        - (void)setCar:(Car *)car
        {
           // 1.先判断是不是新传进来对象
           if ( car != _car )
           {
               // 2.对旧对象做一次release
               [_car release];
               
               // 3.对新对象做一次retain
               _car = [car retain];
           }
        }
      
  6. dealloc方法的代码规范(A死去不再拥有B)
    1. 一定要[super dealloc],而且放到最后面
    2. 对self(当前)所拥有的其他对象做一次release
      - (void)dealloc
      {
         [_car release];
         [super dealloc];
      }
    

将上面的例子中Person中的- (void)setBook:(Book *)book方法修改如下:

- (void)setBook:(Book *)book
{
    // 1.先判断是不是新传进来对象
    if (book != _book) {
        // 2.对旧对象做一次release
        [_book release];
         // 3.对新对象做一次retain
        _book = [book retain];
    }
    
}

main.m的代码如下:

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"

int main()
{
    // b-1
    Book *b = [[Book alloc] init];
    //b2-1
    Book *b2 = [[Book alloc] init];
    // p-1
    Person *p1 = [[Person alloc] init];
    
    // b-2
    [p1 setBook:b];
    //P1 替换新的book b-1 b2-2
     [p1 setBook:b2];
    
    
    // p-0
    // b-1
    [p1 release];
    p1 = nil;
    
    // b-0
    [b release];
    b = nil;
    //b2-0
    [b2 release];
    b2 = nil;
    return 0;
}

@property参数

  1. 之前我们学过的@property Book *book;生成的setter方法,是这样的:

     - (void)setBook:(Book *)book
     {
         _book = book ;
     }
    
  2. 很显然不能满足我们内存管理的要求,那么就要用到property参数


分为三类:

  1. set方法内存管理相关的参数
    1. retain : release旧值,retain新值(适用于OC对象类型)
    2. assign : 直接赋值(默认,适用于非OC对象类型)
    3. copy : release旧值,copy新值
  2. 是否要生成set方法
    1. readwrite : 同时生成setter和getter的声明、实现(默认)
    2. readonly : 只会生成getter的声明、实现
  3. 多线程管理
    1. nonatomic : 性能高 (一般就用这个)
    2. atomic : 性能低(默认)
  4. setter和getter方法的名称
    1. setter : 决定了set方法的名称,一定要有个冒号 :
    2. getter : 决定了get方法的名称(一般用在BOOL类型)

代码举例:

//Person.h文件
#import <Foundation/Foundation.h>
#import "Book.h"
@interface Person : NSObject
//非oc对象
@property (nonatomic,assign) int age;
// retain : 生成的set方法里面,release旧值,retain新值
@property (retain,nonatomic) Book *book;
//字符串用copy
@property (copy,nonatomic) NSString *name;
// 返回BOOL类型的方法名一般以is开头
@property (getter = isRich) BOOL rich;
//只生成get方法
@property (nonatomic,assign,readonly) int height;
@end
//Person.m文件
#import "Person.h"
@implementation Person

- (void)dealloc
{
    [_book release];
    [_name release];
    [super dealloc];
}
@end

//main.m文件
#import <Foundation/Foundation.h>
#import "Person.h"
#import "Book.h"

int main()
{
    Book *b = [[Book alloc] init];
    Person *p = [[Person alloc] init];
    p.book = b;
    //修改getter方法名
    BOOL bx = p.isRich;
    int height = p.height;
    NSLog(@"%ld", [b retainCount]);
    [p release];
    [b release];
    return 0;
}

循环retain和@class

  1. @class
    1. 使用场景
      1. 对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类

         #import"B.h"
         @interface A : NSObject{
             B *_b;
         }
         @end
                    
          #import"A.h"
         @interface B : NSObject{
             A *_a;
         }
         @end
        
      2. 两方分别导入对方的.h文件,这种代码编译会报错。当使用@class在两个类相互声明,就不会出现编译报错

    2. 用法概括
      1. 使用@class 类名; 就可以引用一个类,说明一下它是一个类
      2. 两方至少有一方使用@class ,或者两方都使用@class
      3. 在相应的.m文件中一定要使用#import导入,因为@class仅仅是说明一个类,并没有把这个类的内容导入
    3. 开发中引用一个类的规范!!!
      1. 在.h文件中用@class来声明类
      2. 在.m文件中用#import来包含类的所有东西
  2. @class 和#import的区别
    1. @class Card;@class仅仅是告诉编译器,Card是一个类
    2. #import方式会包含被引用类的所有信息,包括被引用类的变量和方法;@class方式只是告诉编译器在A.h文件中 B *b 只是类的声明,具体这个类里有什么信息,这里不需要知道,等实现文件中真正要用到时,才会真正去查看B类中信息
    3. 如果有上百个头文件都#import了同一个文件,或者这些文件依次被#improt,那么一旦最开始的头文件稍有改动,后面引用到这个文件的所有类都需要重新编译一遍,这样的效率也是可想而知的,而相对来 讲,使用@class方式就不会出现这种问题了
    4. 在.m实现文件中,如果需要引用到被引用类的实体变量或者方法时,还需要使用#import方式引入被引用类
  3. 循环retain
    1. 比如A对象retain了B对象,B对象retain了A对象
    2. 这样会导致A对象和B对象永远无法释放
    3. 解决方案
      1. 当两端互相引用时,应该一端用retain、一端用assign

代码举例:

//Person.h
#import <Foundation/Foundation.h>
#import "Card.h"
// @class仅仅是告诉编译器,Card是一个类
//@class Card;

@interface Person : NSObject
@property (nonatomic, retain) Card *card;
@end
//Person.m
#import "Person.h"
#import "Card.h"

@implementation Person
- (void)dealloc
{
    NSLog(@"Person被销毁了");
    [_card release];
    
    [super dealloc];
}
@end

////Card.h
#import <Foundation/Foundation.h>
@class Person;
@interface Card : NSObject
@property (nonatomic, assign) Person *person;
@end
//Card.m
#import "Card.h"
#import "Person.h"

@implementation Card
- (void)dealloc
{
    NSLog(@"Car被销毁了");
    //assign就不需要release了
    // [_person release];
    
    [super dealloc];
}
@end

//调用:
#import <Foundation/Foundation.h>
#import "Card.h"
#import "Person.h"

int main()
{
    // p - 1
    Person *p = [[Person alloc] init];
    // c - 1
    Card *c = [[Card alloc] init];
    
    // c - 2
    p.card = c;
    
    // p - 1 因为person是assign,所以不会+1
    c.person = p;
    
    // c - 1
    [c release];
    
    // p - 0  c - 0
    [p release];
    return 0;
}

autorelease

  1. autorelease的基本用法
    1. 会将对象放到一个自动释放池中
    2. 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
    3. 会返回对象本身
    4. 调用完autorelease方法后,对象的计数器不变
    5. autorelease实际上只是把对release的调用延迟了,对于每一次autorelease,系统只是把该对象放入了当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会被调用Release
  2. autorelease的好处
    1. 不用再关心对象释放的时间
    2. 不用再关心什么时候调用release
  3. autorelease的使用注意
    1. 占用内存较大的对象不要随便使用autorelease
    2. 占用内存较小的对象使用autorelease,没有太大影响
  4. 错误写法
    1. alloc之后调用了autorelease,又调用release

       @autoreleasepool
        {
           // 1
           Person *p = [[[Person alloc] init] autorelease];
               
           // 0
           [p release];
        }
      
    2. 连续调用多次autorelease

        @autoreleasepool
        {
           Person *p = [[[[Person alloc] init] autorelease] autorelease];
        }
      
  5. 自动释放池
    1. 在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出)
    2. OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)
  6. 自动释放池的创建方式
    1. iOS 5.0前

       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        //...
        [pool release]; // [pool drain];
      
    2. iOS 5.0 开始

       @autoreleasepool
        {
                  
        }
      

    代码举例:

     #import <Foundation/Foundation.h>
        
     @interface Person : NSObject
     @property (nonatomic, assign) int age;
     @end
        
     #import "Person.h"
        
     @implementation Person
     - (void)dealloc
     {
         NSLog(@"Person---dealloc");
            
         [super dealloc];
     }
     @end
        
     //调用:
        
     #import <Foundation/Foundation.h>
     #import "Person.h"
        
     int main()
     {
         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
            
         Person *pp = [[[Person alloc] init] autorelease];
            
         [pool release]; // [pool drain];
            
         @autoreleasepool
         {
            
             // 1
             Person *p = [[[[Person alloc] init] autorelease] autorelease];
                
             // 0
             // [p release];
         }
            
         return 0;
     }
        
     void test()
     {
         @autoreleasepool
         {// { 开始代表创建了释放池
                
             // autorelease方法会返回对象本身
             // 调用完autorelease方法后,对象的计数器不变
             // autorelease会将对象放到一个自动释放池中
             // 当自动释放池被销毁时,会对池子里面的所有对象做一次release操作
             Person *p = [[[Person alloc] init] autorelease];
                
             p.age = 10;
        
             @autoreleasepool
             {
                 // 1
                 Person *p2 = [[[Person alloc] init] autorelease];
                 p2.age = 10;
             }
        
             Person *p3 = [[[Person alloc] init] autorelease];
         } // } 结束代表销毁释放池
     }
        
    
  7. autorelease的应用
    1. 系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
    2. 开发中经常会提供一些类方法,快速创建一个已经autorelease过的对象
      1. 创建对象时不要直接用类名,一般用self

          + (id)person
          {
             return [[[self alloc] init] autorelease];
          }
        

代码举例:

//父类Person
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, assign) int age;
+ (id)person;

+ (id)personWithAge:(int)age;
@end
#import "Person.h"

@implementation Person
+ (id)person
{
    //这里要用self比较好
    return [[[self alloc] init] autorelease];
}
+ (id)personWithAge:(int)age
{
    Person *p = [self person];
    p.age = age;
    return p;
}

- (void)dealloc
{
    NSLog(@"%d岁的人被销毁了", _age);
    
    [super dealloc];
}
@end

//子类
#import "Person.h"
@interface GoodPerson : Person
@property (nonatomic, assign) int money;
@end

#import "GoodPerson.h"
@implementation GoodPerson
@end

//调用
#import <Foundation/Foundation.h>
#import "Person.h"
#import "GoodPerson.h"
int main()
{
    @autoreleasepool {
        Person *p = [Person personWithAge:100];
        //内部用的是self因此释放的是GoodPerson类
        GoodPerson *p2 = [GoodPerson personWithAge:10];
        p2.money = 100;
    }
    return 0;
}

void test()
{
    //不用autorelease
    Person *p = [[Person alloc] init];
    p.age = 200;
    [p release];
    
    // 包
    // Student com.mj.test
    // Student com.mj.test2
    
    // MJStudent
    
    // SBStudent
    
    // Next Step
    
    
    @autoreleasepool
    {
        // 1. 封装
        //直接用autorelease
        //  Person *p2 = [[[Person alloc] init] autorelease];
        //  p2.age = 100;
        //将autuolease封装起来
        //        Person *p2 = [Person person];
        //        p2.age = 100;
        Person *p2 = [Person personWithAge:100];
        
        //2. 系统自带的方法里面没有包含alloc、new、copy,说明返回的对象都是autorelease的
        //例1:
        NSString *str = @"123123";
        NSString *str2 = [NSString stringWithFormat:@"age is %d", 10];
        //例2:
        NSNumber *num = [[NSNumber alloc] initWithInt:10];
        [num release];
        NSNumber *num2 = [NSNumber numberWithInt:100];
    }
}

ARC

  1. ARC是自iOS 5之后增加的新特性,完全消除了手动管理内存的烦琐,编译器会自动在适当的地方插入适当的retain、release、autorelease语句。你不再需要担心内存管理,因为编译器为你处理了一切
  2. ARC 是编译器特性,而不是 iOS 运行时特性,它也不是类似于其它语言中的垃圾收集器。因此 ARC 和手动内存管理性能是一样的,有时还能更加快速,因为编译器还可以执行某些优化
  3. ARC的判断准则:只要没有强指针指向对象,就会释放对象
  4. ARC特点
    1. 不允许调用release、retain、retainCount
    2. 允许重写dealloc,但是不允许调用[super dealloc]
    3. @property的参数
      1. strong :成员变量是强指针(适用于OC对象类型)
      2. weak :成员变量是弱指针(适用于OC对象类型)
      3. assign : 适用于非OC对象类型
  5. 以前的retain改为用strong
  6. 指针分2种:
    1. 强指针:默认情况下,所有的指针都是强指针 __strong
    2. 弱指针:__weak
    3. 弱指针指向的对象被回收后,弱指针会自动变为nil指针,不会引发野指针错误
  7. 当两端循环引用的时候,解决方案:
    1. ARC
      1. 1端用strong,另1端用weak
    2. 非ARC
      1. 1端用retain,另1端用assign
       // 错误写法(没有意义的写法)(这一行过后,对象就会被销毁)
         __weak Person *p = [[Person alloc] init];
            
         //打印nil,p自动为nil,不会出现野指针错误
         NSLog(@"%@", p);
            
         NSLog(@"------------");
    
  8. MRA与ARC之间的转换
    1. 如何查看一个项目是非ARC还是MRC?
      1. 点击项目名->build setting->搜索auto ->Aplle LVVM 9.0 - Language - Objective - C -> Objective - C Automatic Reference Counting - 是否为YES
    2. 在ARC项目中,如何让某一个.m文件支持MRC?
      1. Build Phases -> Compile Sources ->找到相应文件 -双击输入-fno-objc-arc即可
    3. 在MRC项目中,如何让某一个.m文件支持ARC?
      1. Build Phases -> Compile Sources ->找到相应文件 -双击输入-f-objc-arc即可
    4. 如何让一个MRC项目转化为ARC项目?
      1. Xcode ->Edit->Convert-> To Object-C ->ARC
#import <Foundation/Foundation.h>
@class Person;
@interface Dog : NSObject
//一段用weak
@property (nonatomic, weak) Person *person;
@end

#import "Dog.h"

@implementation Dog
- (void)dealloc
{
    NSLog(@"Dog--dealloc");
}
@end

#import <Foundation/Foundation.h>

@class Dog;

@interface Person : NSObject
//一端用strong
@property (nonatomic, strong) Dog *dog;

@end
#import "Person.h"

@implementation Person

- (void)dealloc
{
    NSLog(@"Person--dealloc");
}

@end

//调用
int main()
{
    Person *p = [[Person alloc] init];
    
    
    Dog *d = [[Dog alloc] init];
    p.dog = d;
    d.person = p;

    return 0;
}

Table of Contents