zoukankan      html  css  js  c++  java
  • OC开发系列-内存管理

    概述

    移动设备的内存极其有限,每个app所有占用的内存是有限的。当app所占用的内存比较多时,系统会发出内存警告,这时得回收一些不需要再使用的内存空间。

    任何集成了NSObject的对象都需要手动进行内存管理。因为OC对象存放于堆里面。

    引用计数器

    • 每一个OC对象都有内部有自己的引用计数器。该计数器占用4个字节。从字面可以理解为"多少人在使用这个对象"。
    • 当对象的引用计数器为0时。该对象会才会被释放。
    • 一个对象通过alloc、 copy 、new创建一个对象,该对象默认的引用计数器为1。

    引用计数器的操作

    * 给对象发送一条retain消息,对象的引用计数器+1
    * 给对象发送一条release消息,对象的引用计数器-1
    * 给对象发送一条retainCount消息,对获得当前对象的引用计数器值
    

    dealloc方法

    当一个对象的引用计数器值为0时,这个对象即将被销毁,器占用的内存被系统回收。对象即将被销毁前系统会自动给改对象发送一条dealloc消息。
    通常重写对象的dealloc方法释放相关的资源。
    * 一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面
    * dealloc不能直接调用
    * 一旦对象被回收,它占用的内存就不再可用被称之为僵尸对象。指向僵尸对象的指针被称为野指针。给僵尸对象发消息会造坏访问报错。

    多对象的内存管理

    多个对象进行内存管理,并且对象之间是有联系的,那么管理就会变得比较复杂。
    比如在内存中有一个聊天室对象,多个Person对象共同使用者聊天室对象。

    对上面的实例我们如果想做好聊天室对象跟Person对象的内存管理。需要做到下面两点
    * 只要有人在使用聊天室对象聊天室对象就不能销毁
    * 没有人使用聊天室对象聊天室对象需要销毁

    当有Person对象使用聊天室就需要对聊天室对象的引用计数器+1。且当Person对象销毁之前需要对使用的房间对象引用计数器-1,这样就可以保证以上两点。
    因此可以这样定义一个Person类

    #import <Foundation/Foundation.h>
    #import "ChatRoom.h"
    @interface Person : NSObject
    {
        ChatRoom *_cRoom;
    }
    - (void)setCRoom:(ChatRoom *)cRoom;
    - (ChatRoom *)cRoom;
    @end
    
    #import "Person.h"
    
    @implementation Person
    - (void)setCRoom:(ChatRoom *)cRoom
    {
        if (_cRoom != cRoom) {
            // 1.先对以前的对象计数器-1
            [_cRoom release];
            // 2.调用对象的retain返回对象本身
            _cRoom = [_cRoom retain];
        }
    }
    
    - (ChatRoom *)cRoom
    {
        return _cRoom;
    }
    
    - (void)dealloc
    {
        // 在Person对象即将销毁先对内部的聊天室对象计数器-1
        [_cRoom release];
        [super dealloc];
    }
    @end
    

    Property修饰符

    assign

    默认Proerty修饰符为assig。比如当为Person声明一个成员属性@property int age; 编译器自动转换成@property(assign) int age;

    retain

    Proerty修饰符为retain在生成的set方法会自动生成set方法的内存管理代码

    @property (retain) ChatRoom *cRoom;
    

    retain修饰生成的set方法:

    - (void)setCRoom:(ChatRoom *)cRoom
    {
        if (_cRoom != cRoom) {
            // 1.先对以前的对象计数器-1
            [_cRoom release];
            // 2. 调用对象的retain返回对象本身
            _cRoom = [_cRoom retain];
        }
    }
    

    nonatomic与nonnull

    nonatomic是保证set方法是线程安全的,nonnull则相反。iOS开发一般使用nonatomic性能高。

    @class

    @class主要用于简单的引用一个类。@class与#import有着本质的区别,#import是预处理指令在程序预处理时期对#import的文件进行拷贝,而@class只是告诉编译器@class后面引用的是一个类。

    import有一个特点,只要引用的文件发生的变化,那么import就会重新拷贝一次。比如现在有三个类Person、 Car、 Wheel。Person.h文件import Car.h,Car.h文件import Wheel.h。一旦wheels Wheel.h发生变化,Car.h需要重新拷贝Wheel.h并且Person.h也重新拷贝Car.h。

    @class具体使用
    * 在.h文件中使用@class引用一个类
    * 在.m文件中使用#import包含这个类的.h文件

    @class其他使用场景

    • 对于循环依赖关系来说,比方A类引用B类,同时B类也引用A类 这种嵌套包含的代码编译报错
    • 当使用@class在两个类相互声明,就不会出现编译报错。

    retain的循环引用

    现在有两个类Person与Car。Person中有个成员属性car,Car中有个成员属性person。

    Person类

    #import <Foundation/Foundation.h>
    @class Car;
    @interface Person : NSObject
    
    @property (retain) Car *car;
    @end
    
    #import "Person.h"
    @implementation Person
    
    - (void)dealloc
    {
        [_car release];
        
        [super dealloc];
        NSLog(@"%s", __func__);
    }
    @end
    

    Car类

    #import <Foundation/Foundation.h>
    @class Person;
    @interface Car : NSObject
    
    @property (retain) Person *per;
    @end
    
    #import "Car.h"
    @implementation Car
    
    - (void)dealloc
    {
        
        [_per release];
        
        [super dealloc];
        
        NSLog(@"%s", __func__);
    }
    @end
    

    main.m

    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import "Car.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            Person *p = [[Person alloc] init];
            Car *c = [[Car alloc] init];
            
            // 人赋值一辆车对象
            p.car = c;
            // 车赋值人对象
            c.per = p;
            
            [p release];
            [c release];
        }
        return 0;
    }
    

    main.m代码执行完控制台并没有Person对象与Car对象dealloc方法的打印。因此内存两个对象并未销毁存在内存泄漏。下面画图展现main.m执行完毕内存的情况。

    为什么执行p跟c的release方法,对象没有销毁。因为在给人对象赋值car对象的时候通过set方法会对car对象引用计数器+1,同理对car对象赋值人对象,人对象引用计数器也会+1。

    分别执行两个对象release只是让对象应用计数器-1,因此两个对象都没有销毁。

    那么我们可以尝试让其中一个类Property修饰符修改为assign。注意:修改为assign修饰一方dealloc千万不要在dealloc对对象属性进行release。否则会造成内存坏访问
    此时的Person类

    #import <Foundation/Foundation.h>
    @class Car;
    @interface Person : NSObject
    // 改为assign修饰
    @property (assign) Car *car;
    
    @end
    
    #import "Person.h"
    @implementation Person
    
    - (void)dealloc
    {
        // [_car release]; 此时Propery修饰符为assign不需要对car进行release
        [super dealloc];
        NSLog(@"%s", __func__);
    }
    @end
    

    autorelease

    通过上面讲解,OC多对象的内存管理主要有以下几点:
    * 1.在外界通过alloc、copy、new关键字创建的对象需要释放的时候发送release消息
    * 2.在set方法中对赋值的对象引用计数器+1,可以使用retain关键字自动生成set方法内存管理代码
    * 3.在一个对象即将要销毁前在dealloc方法中对内部引用的对象进行release。

    这里就会发现,我们需要时时刻刻关注对象什么时候需要销毁时发送release消息。接下来就可以通过autorelease的机制来解决手动编写大量的对象的release代码。不需要一直再关注对象什么时候需要release。只要可以访问到对象的作用域都可以使用对象。

    autorelease是一种支持引用计数器的内存管理方式,只要给对象发送一条autorelease消息会将对象放到自动释放池中。当自动释放池销毁,会对池子中的所有对象发送一条release消息。autorelease操作对象的引用计数器。当一个对象发送release消息返回时对象的本身。

    #import <Foundation/Foundation.h>
    #import "Person.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            
            Person *p = [[Person alloc] init];
            // 将对象p添加到autorelease释放池
            p = [p autorelease];
            
            // [p release]; 这里不需要在写release 否则坏访问
        }// 自动释放池销毁会给对象中所有的对象发送一条release消息
        return 0;
    }
    

    在iOS5.0之前autorelease的写法

    int main(int argc, const char * argv[]) {
        
        // 创建一个自动释放池
        NSAutoreleasePool *pool =[[NSAutoreleasePool alloc] init];
        
            Person *p = [[Person alloc] init];
            // 将对象p添加到autorelease释放池
            p = [p autorelease];
        
        [pool drain]; // 自动释放池销毁会给对象中所有的对象发送一条release消息
        return 0;
    }
    
    

    MRC类工厂方法的编写

    我们在初始化一个对象将它添加进自动释放池中

    @autoreleasepool{
            // 创建一个对象
            Person *p = [[[Person alloc] init] autorelease];
        }
    

    可见每次创建一个Person对象都需要写很长的代码,于是我们可以封装内部细节提供一个类工厂方法

    /**快速创建一个Person实例*/
    + (instancetype)person;
    
    + (instancetype)person
    {
        return [[[self alloc] init] autorelease];
    }
    

    外界调用类工厂对象初始化对象

    int main(int argc, const char * argv[]) {
        
        @autoreleasepool{
            // 创建一个对象
            Person *p = [[[Person alloc] init] autorelease];
            
            // 通过类工厂方法 创建的对象默认添加进自动释放池当中
            Person *p1 = [Person person];
        }
        return 0;
    }
    

    我们想让Person在初始化后就有一个默认的名字。我们可以提供自定义构造方法。

    #import <Foundation/Foundation.h>
    @interface Person : NSObject
    
    @property(nonatomic, copy) NSString *name;
    /**快速创建一个Person实例*/
    + (instancetype)person;
    
    /**自定义构造方法*/
    -(instancetype)initWithName:(NSString *)name;
    +(instancetype)personWithName:(NSString *)name;
    @end
    
    #import "Person.h"
    @implementation Person
    /**自定义构造方法*/
    -(instancetype)initWithName:(NSString *)name
    {
        if(self = [super init]){
            self.name = name;
        }
        return self;
    }
    
    + (instancetype)person
    {
        return [[[self alloc] init] autorelease];
    }
    
    +(instancetype)personWithName:(NSString *)name
    {
        // 类方法中将对象添加进自动释放池中
        return [[self initWithName:name] autorelease];
    }
    @end
    

    上面的代码会发现在MRC类方法中生成的对象默认内部将其添加自动释放池当中,其实这也是苹果的规范。苹果Foundation框架中通过类方法创建的对象都是autorelease。

    ARC的内存管理

    ARC是编译器的特性,而不是运行时特性,跟其他语言的垃圾回收有着本质的区别。ARC原理就是在编译阶段为我们在必要的时候添加release这些代码。
    ARC的判断原则
    * 只要还有一个强指针指向对象,对象就会保持在内存中

    ARC与MRC的混编

    * 在ARC项目中执行某个文件使用MRC -fno-objc-arc
    * 在MRC项目中执行某个文件使用ARC -fobjc-arc

    使用Xcode将一个MRC工程转化成ARC项目

  • 相关阅读:
    23)PHP,数组操作函数
    22)PHP,数组排序函数
    21)PHP,杨辉三角
    数据库三大范式
    SQL Server 中关于EXCEPT和INTERSECT的用法
    面试准备
    面试总结
    jQuery函数attr()和prop()的区别
    SVN checkout出某个特定版本
    SVN 中的忽略文件
  • 原文地址:https://www.cnblogs.com/CoderHong/p/8826627.html
Copyright © 2011-2022 走看看