• Objective-C基础笔记(3)OC的内存管理


    Objective-C的内存基本管理

    在OC中每个变量都保存着引用计数器,当这个对象的引用计数器为0的时候该对象会被回收。当使用alloc、new或者copy创建一个对象的时候,对象的引用计数器被置为1.

    给对象发送一条retain消息,可以使引用计数器+1.

    给对象发送一条release消息,可以使引用计数器-1.

    当OC被销毁的时候会发送一条dealloc消息(不要直接调用,由系统调用),可以重写dealloc方法,在该方法中释放相关资源。

    可以给对象发送retainCount消息获取对象的当前引用计数器。

    首先我们新建一个工程


    接下来将工程的设置里面将ARC禁掉


    Book.h文件

    #import <Foundation/Foundation.h>
    
    @interface Book : NSObject
    
    @property float price;
    
    - (id)initBook:(float)price;
    
    @end
    Book.m文件

    #import "Book.h"
    
    @implementation Book
    
    @synthesize price = _price;
    
    //构造函数
    - (id)initBook:(float)price {
        if(self = [super init]){
            _price = price;
        }
        NSLog(@"价格是%f的书购买了", _price);
        return self;
    }
    
    //析构函数
    - (void)dealloc {
        NSLog(@"价格为%f的书被释放了", _price);
        [super dealloc];
    }
    
    @end
    Student.h文件

    #import <Foundation/Foundation.h>
    #import "Book.h"
    
    @interface Student : NSObject
    
    @property int age;
    @property Book *book;
    
    - (void)setBook:(Book *)book;
    
    - (id)initStudent:(int)age;
    
    @end
    Student.m文件

    #import "Student.h"
    #import "Book.h"
    
    @implementation Student
    
    @synthesize age = _age;
    @synthesize book = _book;
    
    - (void)setBook:(Book *)book {
        if(_book != book){
            //先对原来的书计数器减一
            //如果之前为nil不会出错(和java中的空指针不同)
            [_book release];
            [book retain];
            _book = book;
        }
    }
    
    //构造函数
    - (id)initStudent:(int)age {
        if(self = [super init]) {
            _age = age;
        }
        NSLog(@"年龄为%d的学生被创建了", _age);
        return self;
    }
    
    //析构函数
    - (void)dealloc{
        [_book release];
        NSLog(@"年龄为%d的学生被释放了", _age);
        [super dealloc];
    }
    
    @end
    
    main.m文件

    #import <Foundation/Foundation.h>
    #import "Student.h"
    #import "Book.h"
    
    void buyBook(Student *stu) {
        Book *book1 = [[Book alloc] initBook:101.5]; //谁创建谁释放
        stu.book = book1;
        [book1 release];
        Book *book2 = [[Book alloc] initBook:98.5];
        stu.book = book2;
        [book2 release];
    }
    
    void readBook(Student *stu) {
        NSLog(@"年龄是%i的学生在读价格为%f的书", stu.age, stu.book.price);
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //计数器为1
            Student *stu = [[Student alloc] initStudent:21];
            //买书
            buyBook(stu);
            //看书
            readBook(stu);
            //计数器清0,释放内存
            [stu release];
        }
        return 0;
    }
    
    输出结果:

    2014-11-13 23:11:19.510 内存管理[698:46519] 年龄为21的学生被创建了

    2014-11-13 23:11:19.512 内存管理[698:46519] 价格是101.500000的书购买了

    2014-11-13 23:11:19.512 内存管理[698:46519] 价格是98.500000的书购买了

    2014-11-13 23:11:19.512 内存管理[698:46519] 价格为101.500000的书被释放了

    2014-11-13 23:11:19.512 内存管理[698:46519] 年龄是21的学生在读价格为98.500000的书

    2014-11-13 23:11:19.512 内存管理[698:46519] 价格为98.500000的书被释放了

    2014-11-13 23:11:19.512 内存管理[698:46519] 年龄为21的学生被释放了

    @class关键字

    通常引用一个类有两种方法,一种是通过#import,另一种是通过@class.

    #import 的方式会将头文件中的所有信息引入。

    @class 的方式只是说明它是一个类(假如只是声明一个类就不用使用#import).

    #import <Foundation/Foundation.h>
    
    @class Book; //声明Book是一个类
    
    @interface Student : NSObject {
        Book *_book;
    }
    
    @property int age;
    @property Book *book;
    
    - (void)setBook:(Book *)book;
    
    - (id)initStudent:(int)age;
    
    @end
    另外,Student.m中的析构函数我们可以做如下修改

    //析构函数
    - (void)dealloc{
        self.book = nil; //调用setter方法
        [_book release];
        NSLog(@"年龄为%d的学生被释放了", _age);
        [super dealloc];
    }
    self.book = nil; 会调用setter方法,释放对象并将当前类Student的属性_book设为nil.

    @property的参数

    @property的参数格式: @property (参数1, 参数2,...) 类型 名字;

    参数主要分为4类:

    读写属性:readwrite / readonly (是否生成get和set方法)

    setter处理: assign / retain / copy  (和内存管理相关)

    原子性:atomic / nonatomic   (这两个和多线程相关)

    set和get方法名称相关参数:setter(设置生成的set方法名称)/ getter(设置生成的get方法名称)

    改变set和get名称,多是用于BOOL类型的变量

    @property (nonatomic, assign, setter = abc:) int height;  设置set方法名称为 abc:

    说明:

    readonly代表只生成getter方法,默认是readwrite

    assing默认(set方法直接赋值),copy是setter方法release旧值,再copy新值

    retain 要生成符合内存管理原则的set方法(应用于对象类型)(注意:在dealloc方法中释放属性对象)

    atomic(默认),保证getter和setter的原子性,提供多线程安全访问,nonatomic性能高,所以一般是选择nonatomic.

    #import <Foundation/Foundation.h>
    
    @class Book; //声明Book是一个类
    
    @interface Student : NSObject
    //assign参数代表set方法直接赋值(默认的)
    //getter方法是指定getter方法的名字
    @property (nonatomic, assign, getter=getStudentAge) int age;
    //这里的retain代表:release旧值,retain新值
    //(注意,基本数据类型不能写retain参数)
    @property (nonatomic, retain) Book *book;
    
    - (void)setBook:(Book *)book;
    
    - (id)initStudent:(int)age;
    
    @end
    #import "Student.h"
    #import "Book.h"
    
    @implementation Student
    
    //构造函数
    - (id)initStudent:(int)age {
        if(self = [super init]) {
            _age = age;
        }
        NSLog(@"年龄为%d的学生被创建了", _age);
        return self;
    }
    
    //析构函数
    - (void)dealloc{
        self.book = nil; //调用setter方法
        [_book release];
        NSLog(@"年龄为%d的学生被释放了", _age);
        [super dealloc];
    }
    
    @end

    自动释放池

    自动释放池是OC里面的一种内存自动回收机制,一般可以将一些临时变量添加到自动释放池中,统一回收释放。当自动释放池销毁时,池里的所有对象都会调用一次release方法。OC对象只需要发送一条autorelease消息,就会把这个对象添加到最近的自动释放池中(栈顶的释放池)。

    autorelease实际上只是把release的调用延迟了,对于每一次autorelease,系统只是把该对象放入当前的autorelease pool中,当该pool被释放时,该pool中的所有对象会调用一次release方法。

    #import "Student.h"
    #import "Book.h"
    
    @implementation Student
    
    //创建静态方法构造对象
    + (id)student {
        Student *stu = [[[Student alloc] init] autorelease];
        return stu;
    }
    
    //创建带参数的静态方法构造对象
    + (id)studentWithAge:(int)age {
        Student *stu = [self student];
        stu.age = age;
        return stu;
    }
    
    //构造函数
    - (id)initStudent:(int)age {
        if(self = [super init]) {
            _age = age;
        }
        NSLog(@"年龄为%d的学生被创建了", _age);
        return self;
    }
    
    //析构函数
    - (void)dealloc{
        self.book = nil; //调用setter方法
        [_book release];
        NSLog(@"年龄为%d的学生被释放了", _age);
        [super dealloc];
    }
    
    @end
    int main(int argc, const char * argv[]) {
        //代表创建一个自动释放池
        @autoreleasepool {
            //第一种写法
            Student *stu = [[[Student alloc] initStudent:21] autorelease];
            Student *stu1 = [[[Student alloc] initStudent:21] autorelease];
    
        } //当括号结束后,池子将被销毁
        //如果自动释放池被销毁,池里面的所有对象都会调用release方法
        @autoreleasepool {
            //第二种写法
            Student *stu2 = [[Student alloc] initStudent:21];
            
            [stu2 autorelease];
        }
        @autoreleasepool {
            //第三种写法(推荐)
            //不用手动释放
            Student *stu3 = [Student student];
        }
        return 0;
    }

    注意:

    1、如果某个对象调用了多次autorelease方法,则在自动释放池销毁的时候会调用多次release方法进行释放。如下:

        @autoreleasepool {
            Person *p = [[Person alloc] init];
            
            [p autorelease];
            
            [p autorelease];
        }
    p对象会被释放两次。

    2、如果嵌套autoreleasepool,那么对象将在调用了autorelease(添加到池子)的每层自动释放池结束的时候调用一次autorelease,如下:

        Person *p = [[Person alloc] init];
        
        @autoreleasepool {
            
            @autoreleasepool {
               [p autorelease];
            }
            
        }
    对象将在第7行被释放,而下面的情况会出现野指针异常

        Person *p = [[Person alloc] init];
        
        @autoreleasepool {
            [p autorelease];
            
            @autoreleasepool {
               [p autorelease];
            }
            
        }

  • 相关阅读:
    java类,接口浅谈
    人月神话阅读笔记01
    学习进度条14
    学习进度条13
    每日站立会议10(完结)
    每日站立会议09
    每日站立会议08
    构建之法阅读笔记06(完)
    每日站立会议07
    每日站立会议06
  • 原文地址:https://www.cnblogs.com/lanzhi/p/6468746.html
Copyright © 2020-2023  润新知