• 1.2 iOS中内存分配的实现


     

      话说我昨天写了一篇告诉大家怎么泡妹子的文章,但是果然程序员都是不缺妹子的啊!!!好吧,既然不缺,那我们还是继续研究技术吧!还是同一句话,如果有交流技术的同学可以加我的qq:1583042987。

    上一节的例子讲述了objective-c的内存管理方式,什么是引用计数,以及如何施行。对此有了一个粗略的概念之后,这一节,将使用代码来对此做具体的阐述。

       1.2.1 alloc/retain/release/dealloc/autorelease的实现

       通过之前的例子,可以清楚的了解到内存的管理步骤,但是具体在程序中如何实现呢?在objective-c中使用alloc/retain/release/dealloc/autorelease这些方法为基础,通过需求进行操作,接下来通过代码来进一步了解。

         /*

            * 活动开始,并已经有一名学生持有图书资源

            */

           id student = [[NSObject alloc]init];

             /**

              *  另一名学生持有图书资源

              */

          [student retain];

          /**

            *  打印学生持有资源总数

            */

         NSLog(@"How many books is %lu.",(unsigned long)[student retainCount]);

             /*

              * 一名学生释放资源

              */

           [student release];

           /**

            *  打印release后,学生持有资源总数

            */

         NSLog(@"After release,How many books is %lu.",(unsigned long)[student retainCount]);

          /**

          *  将资源注册到自动释放池,当释放池结束时自动调用release释放内存

          */

      [student autorelease];

           /**

            *  打印autorelease后,学生持有资源总数

            */

         NSLog(@"After autorelease,How many books is %lu.",(unsigned long)[student retainCount]);

     

    打印结果为:How many books is 2.

     After release,How many books is 1.

    After autorelease,How many books is 1.

    从代码中可以看出,当调用alloc时对象向系统请求一块内存,生成并持有这块资源,再调用retain方法的时候,持有数量加1,引用计数数量打印结果为2,而调用release则减1,引用计数数量打印结果为2,在调用autorelease 时将此次操作记录到一个名为自动释放池的管理器中,暂时并不释放,引用计数不变。当释放池结束运行时,会将注册在里面的内存释放,如图1.2所示。

          

    图1.2   自动释放池流程

    在引用计数技术下,每一个对象都存有一个计数器,纪录当下对象持有资源数量,当数量为0时,调用dealloc彻底释放这块内存,为避免出现“dangling pointer”(悬挂指针,也称为迷途指针)现象,意为指针指向无效或者已被释放的内存地址,为避免这样的事情发生通常会在计数为0时将对象至为nil确保其安全释放。

    如代码所示,对象的引用计数可以通过retaincount实例方法获得,但这个方法并不实用,原因有三点,一,虽然它保留了对象的计数,但保留计数的绝对值,且永远不会返回0,这就给开发者带来极大的不便,二,它只保留了对象在某一时间点的计数,当使用autorelease时并不能考虑到自动释放池运行结果,那就给开发者带来一个错误的信息,容易导致开发者过度释放,三,在处理如NSString,NSNumber这样基本数据类型时,返回值往往会很大,如下代码。

        /**

         *   为一个字符串赋值,并打印其引用计数

         */

        NSString *str = @"iOS性能优化";

        NSLog(@"str is retainCount = %lu.",(unsigned long)[str retainCount]);

         /**

              *  为一个数字类型的对象复制,并打印其引用技术

          */

          NSNumber *num = @1;

      NSLog(@"num is retainCount = %lu.",(unsigned long)[num retainCount]);

        打印结果为:str is retainCount = 18446744073709551615.

                   num is retainCount = 9223372036854775807.

    此时如果使用retainCount方法查看计数就会非常尴尬,另外在ARC中,retainCount方法已经被禁用。

    在objective-c中还有两种方法可以增加引用计数copy(浅拷贝)和mutablecopy(深拷贝)。使用copy的类必须遵守NSCopying协议规定,该协议只有一个方法:

    /*************** Basic protocols      ***************/

     

    @protocol NSCopying

     

    - (id)copyWithZone:(NSZone *)zone;

     

    @end

    而使用mutablecopy必须遵守NSMutableCopying协议规定,该类也是只有一个方法:

    @protocol NSMutableCopying

     

    - (id)mutableCopyWithZone:(NSZone *)zone;

     

    @end

    对于初学者而言,很难理解zone的含义。在很早的时候,开发系统程序时,会把内存分成很多不同的“区域”(zone),而对象则放在其中。当然现在苹果为了给开发者提供更快速,更方便的环境,将每个程序都只用了一个区域——“默认区域”(default zone)。所以开发者不用再纠结于zone这个参数了。

    两者都可以生成并持有对象的副本,区别在于copy生成的副本不可变,而mutablecopy生成的副本是可变的。调用二者都会让对象的引用计数递增,于使用retain方法不同的是,retain是持有一个新的对象,而copy/mutablecopy则是持有对象本身的副本。那么使用copy以及mutablecopy对程序编写有什么好处呢?

    (NSString mutablecopy) ===>  NSMutableString

     (NSMutableString copy)  ===>  NSString

    如上面为代码所示,使用copy与mutablecopy可以使对象在可变与不可变之间互相切换。另外使用copy/mutablecopy可以将一些对象从“栈内存”(stack)复制到“堆内存”(heap),比如“闭包”(block),当然block这个对象还有很多使用规则,在本节不做过多介绍,后面将会对此进行详细讲解。

    1.2.2  在开发项目中的循环引用问题

    通过上面的介绍,对于objective-c的内存管理多少有一些认识,那接下来结合实际对内存的使用规则进行更深入的了解。

    在使用引用计数机制时,最为需要注意的问题就是“循环引用”(retain cycle),有人也会称之为“内存保留环”,也就是两个或者多个对象间互相引用,导致内存泄露,如图1.3。

                                                  图1.3   循环引用实例图

    从图上可以看出来对象之间互相持有,这就造成至少有一个对象不能正常释放,在这个循环里,所有对象的保留计数都是1。这种情况,常见于类与类之间的联系,如“代理”(delegate)或是“闭包”(block)。下面通过不同代码,对代理与闭包分别展示一下。

    BasicManage.h文件

    #import <Foundation/Foundation.h>

    @class BasicManage;

    @protocol BasicManageDelegate <NSObject>

    /**

     *  设置网络请求完成后回调

     *

     *  @param manage 网络请求管理器

     *  @param data   返回值

     */

    -(void)netWorkManage:(BasicManage *)manage

          didReceiveData:(NSData *)data;

    /**

     *  设置网络请求异常回调

     *

     *  @param manage 网络管理器

     *  @param error   错误信息

     */

    -(void)netWorkManage:(BasicManage *)manage

        didFailWithError:(NSError *)error;

    @end

    @interface BasicManage : NSObject

     

    @property(assign,nonatomic)id<BasicManageDelegate>delegate;

     

    @end

    BasicManage.m文件

    #import "BasicManage.h"

     

    @implementation BasicManage

     

    @end

    BasicModel.h文件

    #import "BasicManage.h"

    @interface BasicModel : NSObject<BasicManageDelegate>

     

    /**

     *  归档网络请求数据

     *

     *  @param data 网络请求数据

     */

    -(void)saveArchiveNetWorkData:(NSData *)data;

     

    @end

    BasicModel.m文件

    #import "BasicModel.h"

     

    @implementation BasicModel

    -(instancetype)init

    {

        self = [super init];

        if (self)

        {

            BasicManage *manage = [BasicManage new];

            manage.delegate     = self;

        }

        return self;

    }

    /**

     *  返回成功方法

     *

     *  @param manage 网络请求管理器

     *  @param data   请求返回值

     */

    -(void)netWorkManage:(BasicManage *)manage didReceiveData:(NSData *)data

    {

        [self saveArchiveNetWorkData:data];

    }

    -(void)netWorkManage:(BasicManage *)manage didFailWithError:(NSError *)error

    {

       

    }

    -(void)saveArchiveNetWorkData:(NSData *)data

    {

       

    }

    @end

     

    此例中delegate属性用assign修饰,原因在于如果使用retain修饰的话,在设置代理方时manage对象被model对象持有,形成循环引用,导致最后dealloc时manage对象不能正常销毁,而使用assign只是简单赋值,并不持有,所以不会造成循环引用的问题。同样的情况也发生在block中,如下代码。

       BasicManage *manage = [BasicManage new];

            manage.delegate     = self;

            [manage receiveDataWithError:^(NSError *error) {

                self.title = [NSString stringWithFormat:@"%@",error];

            }];

    由于在block中使用对象,当block从栈存储区域复制到堆时,对象同时被block强引用持有,此时self已经被block持有,很多初学者,对于block的内存管理不够了解,常常会造成这样的内存问题,导致内存暴增,如何解决这样问题?如下代码。

       BasicModel __weak *obj = self;

            [manage receiveDataWithError:^(NSError *error) {

                obj.title = [NSString stringWithFormat:@"%@",error];

            }];

    这里先不对上述代码做过多解释,后面将通过对block一步步了解之后,解决这些问题。

    通过上述代理对于循环引用的处理方法可以知道,采用简单赋值并不持有的方法,可以有效避免循环引用问题,或者从外界命令循环中的某个对象不在保留另一个对象,这两种方法都可以有效的解决循环引用的问题,从而避免内存泄漏。

  • 相关阅读:
    深入浅出接口测试原理及步骤
    软件测试所需要掌握的技能
    Spring Boot(三)—— 自动装配原理
    Spring Boot(一)—— Spring Boot入门
    线程的六种状态
    有关于java中List.add方法进行添加元素,发生覆盖的问题
    《暗时间》读后感
    《基于UML的高校教务管理系统的设计与实现 》论文笔记(六)
    win7下硬盘安装ubuntu
    词频统计
  • 原文地址:https://www.cnblogs.com/xuruofan/p/5764928.html
Copyright © 2020-2023  润新知