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的操作。那样就可以打破循环引用的环了。

时间: 2024-10-09 15:46:57

Block循环引用问题研究的相关文章

ios block 循环引用

无意中看到有人在咨询block循环引用如何解决的问题:记录下来,方便童鞋们参考 ios开发中,开了ARC模式,系统自动管理内存,如果程序中用到了block就要注意循环引用带来的内存泄露问题了 这几天遇到一个问题,正常页面dismiss的时候是要调用dealloc方法的,但是我的程序就是不调用,研究了好久终于找到了问题出在哪里了 起初的代码如下: - (void)getMyrelatedShops { [self.loadTimer invalidate]; self.loadTimer = [N

block循环引用的简单说明

- (void)viewDidLoad {     [super viewDidLoad];     // 如果我们不对block进行copy操作, 那么block存在于栈区, 栈区的block块不会对引用的对象进行持有     // 如果我们对block进行了copy操作, 那么block就存在于堆区, block块就会对引用的对象进行持有          Student *student = [[Student alloc] init];     // 如何解决循环引用问题     //

和block循环引用说再见

to be block? or to be delegate? 这是一个钻石恒久远的问题.个人在编码中暂时没有发现两者不能通用的地方,习惯上更偏向于block,没有什么很深刻的原因,只是认为block回调写起来更便捷,直接在上下文中写block回调使得代码结构更清晰,可读性更强.而delegate还需要申明protocol接口,设置代理对象,回调方法与上下文环境不能很好契合,维护起来没有block方便.另外初学者很容易会被忘记设置代理对象坑- 然而惯用block是有代价的,最大的风险就是循环引用

关于block 循环引用 weakSelf

什么是block? 代码块:{}里的东西 block可以想id一样装到array里,dictionary里...但是不能对他发送消息. nsdictionary 里有一个方法:enumerateKeysAndObjectUsingBlock:^(id key,id value,BOOL *stop) 这个方法遍历dictionary里的东西,直到*stop = YES为止. block 里的代码对于主线程里的变量什么的都是可读的.除非主线程里的变量加上__block 例如:__block BOO

iOS Block循环引用

前言 本篇文章精讲iOS开发中使用Block时一定要注意内存管理问题,很容易造成循环引用.本篇文章的目标是帮助大家快速掌握使用block的技巧. 我相信大家都觉得使用block给开发带来了多大的便利,但是有很多开发者对block内存管理掌握得不够好,导致经常出现循环引用的问题.对于新手来说,出现循环引用时,是很难去查找的,因此通过Leaks不一定能检测出来,更重要的还是要靠自己的分析来推断出来. 声景一:Controller之间block传值 现在,我们声明两个控制器类,一个叫ViewContr

【少年,放松~】出现block循环引用的三种情况和处理办法

刚入职在看已经上线的项目,其中用到block进行快捷回调的做法很常用,但是Xcode都给给以了如下[循环引用]的警告(如下)的情况,结合网络上的查找和自己的理解,进行总结如下. // Capturing 'self' strongly in this block is likely to lead to a retain cycle 出现这种情况的原因主要是:因为block的特性,声明block的对象都会以copy的形式持有block,而block对于它内部的对象也会进行强引用,从而导致了循环引

ios之block循环引用

在 iOS 4.2 时,苹果推出了 ARC 的内存管理机制.这是一种编译期的内存管理方式,在编译时,编译器会判断 Cocoa 对象的使用状况,并适当的加上 retain 和 release,使得对象的内存被合理的管理.所以,ARC 和 MRC 在本质上是一样的,都是通过引用计数的内存管理方式. 然而 ARC 并不是万能的,有时为了程序能够正常运行,会隐式的持有或复制对象,如果不加以注意,便会造成内存泄露!今天就列举几个在 ARC 下容易产生内存泄露的点,和各位童鞋一起分享下. block 系列

Block循环引用,强引用方法引起的循环引用,CenterX CenterY 不生效的问题

相信大家都用过CenterX 和 CenterY吧,这2个属性确实非常方便,但是有些时候会发现突然设置了CenterX CenterY不好使了,不居中了,一头雾水.这种情况 我建议第一时间去看看设置CenterX CenterY之前有没有设置size 也就是 宽和高.如果在设置宽高之前就设置了CenterX CenterY 肯定是不生效的,原因不用多说了吧,如果是这种情况,将先设置一下size就好了. 上一篇文章提到了block里面用了self从而导致了 循环引用,控制器释放不了,有些朋友知道应

Block循环引用问题

根控制器没办法销毁,除非程序退出 从一个控制器跳到另外一个控制器,调用该控制器的pop方法才会销毁该控制器 self是一个强指针 在block中使用self时要注意循环引用的问题 最好将当前block中的self 强引用换成弱引用 如下图: