博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
循环引用问题
阅读量:7082 次
发布时间:2019-06-28

本文共 4938 字,大约阅读时间需要 16 分钟。

  hot3.png

当多个对象相互持有形成一个封闭的环时,循环引用问题随之出现,导致内存泄漏。

解决循环引用问题主要有两个办法,第一个办法是自己明确知道这里会存在循环引用,在合理的位置主动断开环中的一个引用(置为nil),使得对象得以回收。第二个办法就是使用弱引用。

弱引用的实现原理

弱引用的实现原理是这样,系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。 从这个原理中,我们可以看出,弱引用的使用是有额外的开销的。虽然这个开销很小,但是如果一个地方我们肯定它不需要弱引用的特性,就不应该盲目使用弱引用。举个例子,有人喜欢在手写界面的时候,将所有界面元素都设置成 weak 的,这某种程度上与 Xcode 通过 Storyboard 拖拽生成的新变量是一致的。但是我个人认为这样做并不太合适。因为: 我们在创建这个对象时,需要注意临时使用一个强引用持有它,否则因为 weak 变量并不持有对象,就会造成一个对象刚被创建就销毁掉。 大部分 ViewController 的视图对象的生命周期与 ViewController 本身是一致的,没有必要额外做这个事情。 早先苹果这么设计,是有历史原因的。在早年,当时系统收到 Memory Warning 的时候,ViewController 的 View 会被 unLoad 掉。这个时候,使用 weak 的视图变量是有用的,可以保持这些内存被回收。但是这个设计已经被废弃了,替代方案是将相关视图的 CALayer 对应的 CABackingStore 类型的内存区会被标记成 volatile 类型.

1、delegate与环

//ClassA:@protocol ClssADelegate 
- (void)fuck;@end@interface ClassA : UIViewController@property (nonatomic, strong) id
delegate;@end//ClassB:@interface ClassB ()
@property (nonatomic, strong) ClassA *classA;@end@implementation ClassB- (void)viewDidLoad { [super viewDidLoad]; self.classA = [[ClassA alloc] init]; self.classA.delegate = self;}

如上代码,B强引用A,而A的delegate属性指向B,这里的delegate是用strong修饰的,所以A也会强引用B,这是一个典型的循环引用样例。而解决其的方式大家也都耳熟能详,即将delegate改为弱引用(weak)。

2、block与环

@interface ClassA ()@property (nonatomic, copy) dispatch_block_t block;@property (nonatomic, assign) NSInteger tem;@end@implementation ClassA- (void)viewDidLoad {    [super viewDidLoad];    self.block = ^{        self.tem = 1;    };  }

如上代码,self持有block,而堆上的block又会持有self,所以会导致循环引用,这个例子非常好,因为xcode都能检测出来,报出警告:[capturing self strongly in this block is likely to lead to a retain cycle],当然大部分循环引用的情况xcode是不会报警告的。解决这种循环引用的常用方式如下(这种解决方式可以解决大部分block引起的循环引用,但是有一定缺陷,且看下一节):

@interface ClassA ()@property (nonatomic, copy) dispatch_block_t block;@property (nonatomic, assign) NSInteger tem;@end@implementation ClassA- (void)viewDidLoad {    [super viewDidLoad];    __weak typeof(self) weakSelf = self    self.block = ^{        weakSelf.tem = 1;    };  }

3、结论

如上delegate和block引起的循环引用的处理方式,有一个共同的特点,就是使用weak(弱引用)来打破环,使环消失了。所以,可以得出结论,我们可以通过使用将strong(强引用)用weak(弱引用)代替来解决循环引用。

4、解决block循环引用的深入探索

1)weakSelf与其缺陷

//ClassB是一个UIViewController,假设从ClassA pushViewController将ClassB展示出来@interface ClassB ()@property (nonatomic, copy) dispatch_block_t block;@property (nonatomic, strong) NSString *str;@end@implementation ClassB- (void)dealloc {}- (void)viewDidLoad {    [super viewDidLoad];    self.str = @"111";    __weak typeof(self) weakSelf = self;    self.block = ^{        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{            NSLog(@"%@", weakSelf.str);        });    };    self.block();   }

这里会有两种情况:

若从A push到B,10s之内没有pop回A的话,B中block会执行打印出来111。 若从A push到B,10s之内pop回A的话,B会立即执行dealloc,从而导致B中block打印出(null)。这种情况就是使用weakSelf的缺陷,可能会导致内存提前回收。

2)weakSelf和strongSelf

@interface ClassB ()@property (nonatomic, copy) dispatch_block_t block;@property (nonatomic, strong) NSString *str;@end@implementation ClassB- (void)dealloc {}- (void)viewDidLoad {    [super viewDidLoad];    self.str = @"111";    __weak typeof(self) weakSelf = self;    self.block = ^{        __strong typeof(self) strongSelf = weakSelf;        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{            NSLog(@"%@", strongSelf.str);        });    };    self.block();   }

我们发现这样确实解决了问题,但是可能会有两个不理解的点。

这么做和直接用self有什么区别,为什么不会有循环引用:外部的weakSelf是为了打破环,从而使得没有循环引用,而内部的strongSelf仅仅是个局部变量,存在栈中,会在block执行结束后回收,不会再造成循环引用。 这么做和使用weakSelf有什么区别:唯一的区别就是多了一个strongSelf,而这里的strongSelf会使ClassB的对象引用计数+1,使得ClassB pop到A的时候,并不会执行dealloc,因为引用计数还不为0,strongSelf仍持有ClassB,而在block执行完,局部的strongSelf才会回收,此时ClassB dealloc。 这样做其实已经可以解决所有问题,但是强迫症的我们依然能找到它的缺陷:

block内部必须使用strongSelf,很麻烦,不如直接使用self简便。 很容易在block内部不小心使用了self,这样还是会引起循环引用,这种错误很难发觉。 不要用NSString和NSNumber测试引用计数 最好使用自定义的class.

3)@weakify和@strongify 查看github上开源的libextobjc库,可以发现,里面的EXTScope.h里面有两个关于weak和strong的宏定义。

// 宏定义#define weakify(...) \    ext_keywordify \    metamacro_foreach_cxt(ext_weakify_,, __weak, __VA_ARGS__)#define strongify(...) \    ext_keywordify \    _Pragma("clang diagnostic push") \    _Pragma("clang diagnostic ignored \"-Wshadow\"") \    metamacro_foreach(ext_strongify_,, __VA_ARGS__) \    _Pragma("clang diagnostic pop")// 用法@interface ClassB ()@property (nonatomic, copy) dispatch_block_t block;@property (nonatomic, strong) NSString *str;@end@implementation ClassB- (void)dealloc {}- (void)viewDidLoad {    [super viewDidLoad];    self.str = @"111";    @weakify(self)    self.block = ^{        @strongify(self)        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{            NSLog(@"%@", self.str);        });    };    self.block();   }

即使写了weak,strong。也得在block里面首先判断strong存在不存在,然后向下进行。 可以看出,这样就完美解决了3中缺陷,我们可以在block中随意使用self。

转载于:https://my.oschina.net/Jacedy/blog/837606

你可能感兴趣的文章
Zend server最大化应用程序的性能、扩展性和可用性
查看>>
Mac OSX操作系统安装和配置Zend Server 6教程(4)
查看>>
python进阶学习路线(全)
查看>>
浏览器加载与渲染
查看>>
HTTP常见错误返回代码
查看>>
安装homeassistant+python3.6
查看>>
老李分享:JDK,JRE,JVM区别与联系 1
查看>>
CentOS 7 上systemctl 的用法
查看>>
Android Design框架
查看>>
[Linux] 在 Linux CLI 使用 ssh-keygen 生成 RSA 密钥
查看>>
在 Ubuntu 16.04 Server 上安装 Zabbix
查看>>
Netgear wndr3700v2 路由器刷OpenWrt打造全能服务器(九)ftp服务
查看>>
oracle 存储过程的基本语法
查看>>
程序员应该遵守的编程原则
查看>>
各操作系统配置NTP
查看>>
使用mysql索引技巧及注意事项
查看>>
按照发起的方式,DDoS可以简单分为三类
查看>>
2月新书,送出一本你爱的!
查看>>
2018LinuxCon,开源界的大咖们来了,赶紧行动!
查看>>
10月24日程序员关爱日
查看>>