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 = ;
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 + ;
}
@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 = ;
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 + ;
};
}
@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 = ;
};
}
@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 = ;
};
}
@end
请注意这两段代码中唯二的差别(加粗的代码段)。在Block外创建一个对于self的弱引用,然后在Block内引用self的地方全部使用这个弱引用。这样就使得Block内部不会对self本身做引用计数+1的操作。那样就可以打破循环引用的环了。
Block循环引用问题研究的更多相关文章
- ios - block循环引用Demo示例
当实例变量中有了block属性,并且用copy来修饰,但是当调用block中的代码的时候,如果block中运用了self.属性的时候回造成循环引用. // // ViewController.h // ...
- iOS Block循环引用
在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...
- IOS block 循环引用的解决
在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...
- iOS中Block循环引用的问题
说到循环引用问题,想必大家都碰到过吧,比如在使用Block的时候,使用__weakSelf来代替self解决等,但是对于这个,还是有不少可以探索的点,下面我就来说下,希望对大家有所帮助. 是否所有的B ...
- Block循环引用问题(Objective-c)
造成循环引用的简单理解是:Block的拥有者在Block作用域内部又引用了自己,因此导致了Block的拥有者永远无法释放内存,就出现了循环引用的内存泄漏 示例代码 @interface ObjTest ...
- Block循环引用详解
前言 在项目中经常用到block,使用不当就很容易因为循环引用而造成内存泄漏.本文分析了block循环引用形成原因以及处理办法,如果有什么不对或者疑问请留言. 什么情况下block会造成循环引用 bl ...
- ios block循环引用问题
ios开发中,开了ARC模式,系统自动管理内存,如果程序中用到了block就要注意循环引用带来的内存泄露问题了 这几天遇到一个问题,正常页面dismiss的时候是要调用dealloc方法的,但是我的程 ...
- block循环引用
block里边会有循环引用的风险,它可能对外部一个变量出现强引用,所以需要判断里边是否有循环引用,通过dealloc方法(销毁当前控制器.或销毁要测试的变量),判断是否循环引用.主要在block 里边 ...
- Block 循环引用(中)
不会造成循环引用的block 大部分GCD方法 dispatch_async(dispatch_get_main_queue(), ^{ [self doSomething]; }); 因为self并 ...
随机推荐
- Arcgis Server发布服务
提到Arcgis Server 服务的发布,做起来貌似很简单,就算电脑再卡,只要鼠标还能点,一个小时肯定能搞定,但是当你遇到问题的时候,就头大了,也许搞上个一两天都摸不着头脑,最后你采取的措施可能是一 ...
- sqllite
public static void tt() { int ID = 0; while(true) { stri ...
- quartz 框架定时任务,使用spring @Scheduled注解执行定时任务
配置quartz 在spring中需要三个jar包: quartz-1.6.5.jar.commons-collections-3.2.jar.commons-logging-1.1.jar 首先要配 ...
- MySQL root密码找回
以MySQL多实例为例,演示找回MySQL root的密码 1.关闭mysql服务 [root@mysql ~]# mysqladmin -uroot -poldboy123 -S /data/330 ...
- sed 命令使用
ios 的sed 命令 跟linux sed 命令有区别 # 所有的a 替换成b sed -i "" 's/a/b/g' #删除掉所有包含a的行 sed -i "/a/ ...
- C# IntPtr转换为Byte[]
[DllImport("OpenNetStream.dll")] public static extern int OpenSDK_Data_GetDevList(IntPtr s ...
- jQuery事件对象event的属性和方法
事件处理(事件对象.目标元素的获取,事件对象的属性.方法等)在不同浏览器之间存在差异,jQuery在遵循W3C规范的情况下做了封装统一 一.事件对象常用的属性: event.type:获取事件的类型, ...
- js-我理解的闭包
一:什么是闭包 <JS高级程序设计>指出:闭包是指有有权访问另一个函数作用域中变量的函数. 二:闭包的使用 闭包的常见的创建方式是 子函数嵌套在父函数的内部,这样,子函数就可以访问父函数中 ...
- AFN中的PATCH 和 DELETE 请求方式
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(nullable id)parameters su ...
- 在Spring MVC项目中,注解方式使用 .properties 文件及 UTF-8编码问题
xml配置 <!-- 配置文件 --> <bean id="configProperties" class="org.springframework.b ...