• Block循环引用问题研究


      自从苹果在objc中添加Block功能支持以后已经过了很久。目前网上对于Block的使用有很多介绍。不过对于Block的内存管理问题,则是众说纷纭。再加上objc开始使用ARC以后,对于Block的内存管理又有了新的变化。因此在本文中笔者将根据自己的理解梳理一下Block的内存管理问题。

    1.Block简单原理

      首先Block的原理要说起来还是挺简单的,就是将一个函数本身当成参数进行传递。而Block的优势就在于它不止可以访问自己函数作用域内的数据,它也可以访问自己作用域范围外的数据。当然,这也是Block内存管理出现困扰的源头。

      当然,即使Block的内存管理需要特别关注。但是从工程框架来说,Block确实有存在的必要。比如在使用Block之前,当我们在一个对象(A)中需要另一个对象(B)给出解决方案的时候,我们通常会用代理的方式在A中将需要的参数传递给B,然后等待B提供的解决方案处理完以后再继续后续操作。

    ObjA.h
    @protocol ObjADelegate <NSObject>
    - (NSInteger)doSomething:(NSInteger)value;
    @end
    @interface ObjA {
        __weak id<ObjADelegate> _delegate;
    }
    @property (nonatomic, weak) id<ObjADelegate> delegate;
    @end
    
    ObjA.m
    @implement ObjA
    @synthesize delegate = _delegate;
    - (void)function {
        NSInteger value = 100;
        if ([_delegate respondsToSelector:@selector(doSomethings:)]) {
           value =  [_delegate doSomethings:value];
        }
        NSLog(@"value: %zd", value);
    }
    @end
    
    ObjB.h
    @interface ObjB <ObjADelegate>
    @end
    
    ObjB.m
    @implement ObjB
    #pragma mark - ObjADelegate
    - (NSInteger)doSomething:(NSInteger)value {
        return value + 100;
    }
    @end

      事实上在仅仅只有一个代理的时候,Block并不见得比代理方便。但是当一个对象成为了多个代理的实现对象的时候,就会使得这个对象的代码变的非常臃肿,也很难以管理。比如下面这个类,光是头文件就能把人看晕了:

    @interface MKModelPagesViewController : UIViewController <UIScrollViewDelegate, MKPageViewDelegate, MKModelAddPageViewDelegate,
    MKModelPreviewDelegate, MKProductInfoDelegate, MKPagePhotoEditBarDelegate, MKPageThemeListViewDelegate,
    MKModelFilterViewNewDelegate, MKPhotoSaveViewDelegate>

      在有多个代理的情况下,使用Block方式就可以使得代码不再那么臃肿:

    ObjA.h
    @interface ObjA
    @property (copy, nonatomic) NSInteger (^doSomethings)(NSInteger value);
    @end
    
    ObjA.m
    @implement ObjA
    - (void)function {
        NSInteger value = 100;
        if (self.doSomethings) {
           value = self.doSomethings(value);
        }
        NSLog(@"value: %zd", value);
    }
    @end
    
    ObjB.h
    @interface ObjB
    @end
    
    ObjB.m
    @implement ObjB
    - (void)anotherFunction {
        ObjA* a = [ObjA new];
        a.doSomethings = ^(NSInteger value) {
            return value + 100;
        };
    }
    @end

      可以看到,使用Block以后代码的结构比使用代理时候要更清晰。当然考虑到根据项目复杂程度,对象之间的通信频率的高低,我们可以按照自己的喜好选择使用Block还是代理。

    2.Block内存管理

      在苹果使用ARC管理之前,Block的内存管理需要区分是Global(全局)、Stack(栈)还是Heap(堆)。而在使用了ARC之后,苹果自动会将所有原本应该放在栈中的Block全部放到堆中,所以这使得我们现在的讨论可以省去很大一部分的麻烦。下面我们就只讨论ARC环境下全局Block和堆Block的内存管理。

      首先,全局的Block比较简单,一句话就可以讲完:凡是没有引用到Block作用域外面的参数的Block都会放到全局内存块中,在全局内存块的Block不用考虑内存管理问题。(放在全局内存块是为了在之后再次调用该Block时能快速反应,当然没有调用外部参数的Block根本不会出现内存管理问题)。

      所以Block的内存管理出现问题的,绝大部分都是在堆内存中的Block出现了问题。实际上属于Block特有的内存管理问题就只有一个:循环引用。

    循环引用

      Block的循环引用是比较容易被忽视,原本也是相对比较难检查出来的问题。当然现在苹果在XCode编译的层级就已经做了循环引用的检查,所以这个问题的检查就突然变的没有难度了。

      简单说一下循环引用出现的原理:Block的拥有者在Block作用域内部又引用了自己,因此导致了Block的拥有者永远无法释放内存,就出现了循环引用的内存泄漏。下面举个例子说明一下:

    @interface ObjTest () {
        NSInteger testValue;
    }
    @property (copy, nonatomic) void (^block)();
    @end
    
    @implement ObjTest
    - (void)function {
        self.block = ^() {
            self.testValue = 100;
        };
    }
    @end

      在这个例子中,ObjTest拥有了一个名字叫block的Block对象;然后在这个Block中,又对ObjTest的一个成员变量testValue进行了赋值。于是就产生了循环引用:ObjTest->block->ObjTest。

      要避免循环引用的关键就在于破坏这个闭合的环。在目前只考虑ARC环境的情况下,笔者所知的只有一种方法可以破坏这个环:在Block内部对拥有者使用弱引用。

    @interface ObjTest () {
        NSInteger testValue;
    }
    @property (copy, nonatomic) void (^block)();
    @end
    
    @implement ObjTest
    - (void)function {
        __weak ObjTest* weakSelf = self;
        self.block = ^() {
            weakSelf.testValue = 100;
        };
    }
    @end

      请注意这两段代码中唯二的差别(加粗的代码段)。在Block外创建一个对于self的弱引用,然后在Block内引用self的地方全部使用这个弱引用。这样就使得Block内部不会对self本身做引用计数+1的操作。那样就可以打破循环引用的环了。

  • 相关阅读:
    ASP.NET MVC 3: Razor中的@:和语法
    如何设置VS的代码智能提示
    七次
    不知不觉
    一切一切
    什么是喜欢
    Oracle的substr函数简单用法与substring区别
    前端必读:浏览器内部工作原理(转载)
    sublime text 插件安装 Mac版的
    一个随机上翻的小效果
  • 原文地址:https://www.cnblogs.com/eagley/p/5434485.html
Copyright © 2020-2023  润新知