iOS内存管理理解

作者: Jadyn_Wu | 来源:发表于2018-04-26 14:29 被阅读5次

    在Build Phases -> Compile Sources -> 对应的文件加上-fno-objc-arc的编译参数可以启用MRC模式

    1.简介

    1.1 内存管理的思考方式

    • 自己生成的对象,自己持有
    • 非自己生成的对象,自己也能持有(指针指向这个对象,引用计数+1,就是持有)
    • 不再需要自己持有的对象时释放
    • 非自己持有的对象无法释放

    1.2 ARC下的内存管理

    ARC虽然能够解决90%的内存管理问题,另外还有10%的需要开发者自己处理

    1. 过度使用block,无法解决循环引用问题
    2. 遇到底层Core Foundation对象,需要自己手动管理它们的引用计数时

    2.引用计数(Reference Count)

    2.1 什么是引用计数

    引用计数是一种管理对象生命周期的方式,一个内存块被强指针指向的数量。

    2.2 alloc/retain/release/dealloc

    cocoa框架中foundation框架类库的NSObject类是担负着内存管理的

    • alloc: 生成并持有对象。
      自己持有的对象可以使用alloc/new/copy/mutableCopy开头命名,如果自己不持有不能是有这些作为开头。
    • retain:持有对象。
      引用计数+1,超过最大数时会报错。寻址找到对象内存地址头部,减去存放引用计数值的struct的大小的地址,获取到计数值并+1,当计数值为超过最大值时抛出异常代码。
    • release:释放对象。
      引用计数-1,如果释放了非自己持有的对象或者释放了引用计数为0的对象,程序会崩溃。寻址找到对象内存地址头部,减去存放引用计数值的struct的大小的地址,获取到计数值并-1,当计数值为0时调用dealloc方法。
    • dealloc:废弃对象。
      废弃由alloc开辟的内存块

    指向一个内存块的每一条指针,都能使用retain和release对这个内存块的引用计数进行改变。

    //开辟内存块,引用计数为1
    id object1 = [[NSObject alloc] init];
    //object2的指针指向object1创建的内存块,内存块的引用计数还是1
    id object2 = object1;
    //object2使用retain是引用计数+1
    [object2 retain];
    //object1使用release将内存块上的引用计数-1
    [object1 release];
    //object1还是可以使用release将内存块的引用计数再-1
    //此时内存块上的引用计数值为0,内存空间被系统回收
    //tips:系统知道马上就要回收内存了,没必要-1了,直接回收对象
    [object1 release];
    

    如果想要销毁一个对象,不仅需要使用release将引用计数-1,还需要将对象的指针置为nil

    id object1 = [[NSObject alloc] init];
    [object1 release];
    object1 = nil;
    

    2.3 引用计数的实现

    GNUstep中的实现:采用在内存块头部放置引用计数来进行管理

    1. 分配存放对象所需要的内存空间,将该内存空间置0
    2. 用一个整数来记录retain的引用计数值,并把这个整数写到内存空间的头部
    3. 返回对象指针

    GNUstep优点:

    1. 少量代码就能完成
    2. 能够统一管理引用计数用的内存块和对象用内存块

    苹果源码中的实现:采用引用技术表(散列表)管理引用计数。

    1. 分配存放对象所需要的内存空间,将空间置0
    2. 在散列表中加入引用计数并附带对象的内存块地址
    3. 返回对象指针

    苹果源码优点:

    1. 对象用内存块分配无需考虑内存块头部
    2. 引用技术表各记录中存有内存块地址,可从各个记录追溯到各个内存块地址,在调试时,只要引用计数表没有被破坏,就可以确认内存块位置,就可以检测各对象持有者是否存在。

    2.4 别向已经释放的对象发消息

    当一个对象引用计数为0时,系统已经将该对象的地址回收,它的输出结果是不一定的,如果被回收的地址为空,则会输出0,如果地址被内存复用了,就会造成系统崩溃。

    NSObject *object = [[NSObject alloc] init];
    NSLog(@"Reference Count = %u", [object retainCount]);
    [object release];
    //此时object的内存被释放了,输入为0或者崩溃
    NSLog(@"Reference Count = %u", [object retainCount]);
    

    3.循环引用(Reference Cycle)

    如果对象A的成员变量是对象B,对象B的成员变量是A,释放对象A需要先释放成员变量B,而释放成员变量B也需要先释放成员变量A,形成一个无法释放的循环,造成内存泄漏,这就是循环引用。

    - (void)viewDidLoad {
        [super viewDidLoad];
        CustomView *view = [[CustomView alloc] init];
        [self.view addSubview:view];
        view.callbackBlock = ^{
            //view持有的block中调用了self,意味着view成员变量的一根指针指向了self
            [self doSomething];
        }
    }
    

    当 viewDidLoad 方法执行时,创建一个 block 并赋值给对象 view 的 callbackBlock 属性,callbackBlock捕捉 self,self 持有 self.view, v 在 addSubview 后成为 self.view 的子 view 而被 self.view 持有,这样就形成了一个引用循环,self -> self.view -> view -> callBackBlock -> self。

    环路越是大的循环引用,越难以被发现。

    打破循环方法1:主动断开循环引用

    根据业务逻辑主动断开引用,在view 执行完callbackBlock后,将block置为nil,主动断开循环引用。

    if (self.callbackBlock) {
        self.callbackBlock();
        //调用方将指向callbackBlock的指向指向了nil,主动释放block
        self.callbackBlock = nil;
    }
    

    循环变成在view -> callBackBlock处被主动断开,循环引用打破

    解决方法2:弱引用

    以代理模式为例,对象A中创建对象B并拿到它的delegate,如果delegate为strong声明的,则会形成循环引用A -> B -> BDelegate -> A,而事实上delegate对象通常都被声明为weak型变量,就是为了避免循环引用,从BDdelegate -> A处打破循环引用。

    系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。每当一个对象的引用计数为 0 时,系统就通过这张表,找到指向对象的所有弱引用指针,把它们置成 nil。

    weakSelf 和 strongSelf:

    weakSelf是将一根weak声明的指针指向了self,在self -> self.view -> view -> callBackBlock -> self循环中,weakSelf使用弱引用的方式从callBackBlock -> self处打破循环引用。

    - (void)viewDidLoad {
        [super viewDidLoad];
        CustomView *view = [[CustomView alloc] init];
        [self.view addSubview:view];
        __weak __typeof__(self) weakSelf = self;
        view.callbackBlock = ^{
            [weakSelf doSomething];
        }
    }
    

    但是使用weakSelf存在一个 callbackBlock 执行时self对象会释放的问题,如果在刚刚执行callbackBlock 中的方法时 weakSelf 就为 nil了,那么callbackBlock中的所有方法中的weakSelf都会是nil,callbackBlock的输出结果是唯一的,不会造成什么影响。但是如果self是在callbackBlock执行到一半的时候释放的,就会导致 callbackBlock出现多种不同的执行结果,这种时候就需要利用strongSelf来保证在所在block的作用域中self不被释放。

    - (void)viewDidLoad {
        [super viewDidLoad];
        CustomView *view = [[CustomView alloc] init];
        [self.view addSubview:view];
        //将指向self的指针变成弱指针,这样不会造成循环引用
        __weak __typeof__(self) weakSelf = self;
        view.callbackBlock = ^{
            //保证在所在block的作用域中self不被释放
            //如果不加strongSelf,可能会出现在doSomething时self还存在,而在执行doAnoterThing时,self变成了nil
            __typeof__(self) strongSelf = weakSelf;
            [strongSelf doSomething];
            [strongSelf doAnoterThing];
        }
    }
    

    在嵌套block中,每个block中都需要设置strongSelf。

    DemoViewController.m
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        CustomView *view = [[CustomView alloc] init];
        [self.view addSubview:view];
        //将指向self的指针变成弱指针,这样不会造成循环引用
        __weak __typeof__(self) weakSelf = self;
        view.callbackBlock = ^{
            //保证在callbackBlock作用域内的self一直不释放
            //如果self进入block时就为nil,则一直为nil。
            __typeof__(self) strongSelf = weakSelf;
            [strongSelf doSomething];
            [strongSelf doAnoterThing];
            obj.objCallbackBlock = ^{
                //需要重新设置strongSelf,保证在objCallbackBlock作用域内的self一直不释放
                __typeof__(self) strongSelf = weakSelf;
                [strongSelf doObjSomething];
                [strongSelf doObjAnotherThing];
            }
        }
    }
    

    4.Core Foundation

    • __bridge
      只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
      • Core Foundation对象 -> Objective-C对象
        NSMutableString *mString;
        CFMutableStringRef cfstr;
        {
        //通过CFCreate系列方法创建,内存块引用计数为1
        CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
        //由于mString是__strong修饰符修饰的,mString指向内存快时引用计数++
        //引用计数为2
        mString = (__bridge NSMutableString *)cfstring;
        cfstr = cfstring;
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
        }
        //由于CF对象不会自动释放,所以引用计数为2
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
      • Objective-C对象 -> Core Foundation对象
        CFMutableStringRef cfstr;
        {
        //通过alloc方法创建内存块,内存块引用计数为1
        NSMutableString *mString = [[NSMutableString alloc] init];
        //通过__bridge转换,内存块上的引用计数不变,依旧为1
        cfstr = (__bridge CFMutableStringRef)mString;
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
        }
        //内存块通过Arc机制释放,cfstr所指为野指针,崩溃或为未知对象
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
    • __bridge_retained
      类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。
      • Core Foundation对象 -> Objective-C对象 ERROR
        NSMutableString *mString;
        CFMutableStringRef cfstr;
        {
        //通过CFCreate系列方法创建,内存块引用计数为1
        CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
        //由于mString是__strong修饰符修饰的,mString指向内存快时引用计数++
        //但由于是__bridge_retained,引用计数又会+1,在ARC下编译会报错
        mString = (__bridge_retained NSMutableString *)cfstring;
        cfstr = cfstring;
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
        }
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));

      • Objective-C对象 -> Core Foundation对象
        CFMutableStringRef cfstr;
        {
        //通过alloc方法创建内存块,内存块引用计数为1
        NSMutableString *mString = [[NSMutableString alloc] init];
        //因为是__bridge_retained 所以引用计数++
        //内存块引用计数为2
        cfstr = (__bridge_retained CFMutableStringRef)mString;
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
        }
        //ARC下出了作用域,mString被释放,引用计数--
        //引用计数为1
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));

    • __bridge_transfer
      类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。
      • Core Foundation对象 -> Objective-C对象
        NSMutableString *mString;
        CFMutableStringRef cfstr;
        {
        //通过CFCreate系列方法创建,内存块引用计数为1
        CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
        //因为是__bridge_transfer 所以引用计数交由OC对象管理
        //引用计数为1 不变
        mString = (__bridge_transfer NSMutableString *)cfstring;
        cfstr = cfstring;
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
        }
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));

      • Objective-C对象 -> Core Foundation对象 ERROR
        CFMutableStringRef cfstr;
        {
        //通过alloc方法创建内存块,内存块引用计数为1
        NSMutableString *mString = [[NSMutableString alloc] init];
        //因为是__bridge_transfer 所以引用计数交由CF对象管理
        //在ARC下编译会报错
        cfstr = (__bridge_transfer CFMutableStringRef)mString;
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
        }
        NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));

    • 总结
      1. Objective-C对象 -> Core Foundation对象用__bridge或__bridge_retain。
      2. Core Foundation对象 -> Objective-C对象用__bridge或__bridge_transfer。

    相关文章

      网友评论

        本文标题:iOS内存管理理解

        本文链接:https://www.haomeiwen.com/subject/vboplftx.html