iOS 循环引用讲解(中)
谈到循环引用,可能是delegate为啥非得用weak修饰,可能是block为啥要被特殊对待,你也可能仅仅想到了一个weakSelf,因为它能解决99%的关于循环引用的事情。下面我以个人的理解谈谈循环引用,读完这篇文章,大约需要15-20分钟的时间。
一、循环引用的产生
当A对象里面强引用了B对象,B对象又强引用了A对象,这样两者的retainCount就一直都无法为0于是内存无法释放,导致内存泄露,所谓的内存泄露,本应该释放的对象,在生命周期结束之后依旧存在。换句话说:得说下内存中和变量有关的分区:堆、栈、静态区。其中,栈和静态区是操作系统自己管理的,对程序员来说相对透明,所以,一般我们只需要关注堆的内存分配,而循环引用的产生,也和其息息相关,即循环引用会导致堆里的内存无法正常回收。如下图:

此处:若想释放内存,需要A的引用计数为0,而B对象持有A,所以想要A dealloc,需要B发送release消息到A。而B只有dealloc的时候才会发送release消息到A,并且B dealloc也需要A发送release消息到B。这样A和B相互等待对方的release消息,造成循环引用,内存无法释放。
二、循环引用的情况
下面我们分析造成循环引用的几种情况:
1.delegate与环
@protocol ClssADelegate
- (void)eat;
@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 = ;
};
}
如上代码,self持有block,而堆上的block又会持有self,所以会导致循环引用,这个例子非常好,因为xcode都能检测出来,报出警告:[capturing self strongly in this block is likely to lead to a retain cycle],当然大部分循环引用的情况xcode是不会报警告的。解决这种循环引用的常用方式如下:
@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 = ;
};
}
结论:
如上delegate和block引起的循环引用的处理方式,有一个共同的特点,就是使用weak(弱引用)来打破坏,使环消失了,所以得出结论,我们可以通过将Strong(强引用)用weak来代替来解决循环引用。
>>>>>>>拓展
(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 = @"";
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf.str);
});
};
self.block();
}
这里有两种情况:
(!)若从Apush到B,10s之内没有pop回到A的话,B中block会执行打印出来111.
(!!)若从Apush到B,10s之内pop回A的话(B控制器已经释放掉了),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 = @"";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( * 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。
这样做其实已经可以解决所有问题,但是强迫症的我们依然能找到它的缺陷:
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 = @"";
@weakify(self)
self.block = ^{
@strongify(self)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)( * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", self.str);
});
};
self.block();
}
可以看出,这样就完美的解决了上述的缺陷,我们可以在block随意使用self。
3.NSTimer的循环引用
使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时。例如:
_timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(startCounting) userInfo:nil
repeats:YES];
类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。
解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这个计时器时,就调用
[_timer invalidate];
_timer = nil;
这样就打破了保留环,类也可以正确释放。但是,这种依赖于开发者手动调用方法,才能让内存正确释放的方式不是一个非常好的处理方式。所以需要另外一种解决方案。如下所示:
@interface NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
block:(void(^)())block
repeats:(BOOL)repeats;
@end @implementation NSTimer (JQUsingBlock) + (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
block:(void(^)())block
repeats:(BOOL)repeats{ return [self scheduledTimerWithTimeInterval:ti
target:self
selector:@selector(jq_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
} + (void)jq_blockInvoke:(NSTimer *)timer{ void(^block)() = timer.userInfo;
if (block) {
block();
}
} @end
定义一个NSTimer的类别,在类别中定义一个类方法。类方法有一个类型为块的参数(定义的块位于栈上,为了防止块被释放,需要调用copy方法,将块移到堆上)。使用这个类别的方式如下:
__weak ViewController *weakSelf = self;
_timer = [NSTimer jq_scheduledTimerWithTimeInterval:5.0
block:^{
__strong ViewController *strongSelf = weakSelf;
[strongSelf startCounting];
}
repeats:YES];
NSTimer对类的保留,从而打破了循环引用的产生。__strong ViewController *strongSelf = weakSelf主要是为了防止执行块的代码时,类被释放了。在类的dealloc方法中,记得调用[_timer invalidate]。今天先到此为止,改天继续讲解@property与Ivar等区别!!!
iOS 循环引用讲解(中)的更多相关文章
- iOS循环引用
iOS循环引用 当前类的闭包/Block属性,用到了当前类,就会造成循环引用 此闭包/Block应该是当前类的属性,我们经常对Block进行copy,copy到堆中,以便后用. 单方向引用是不会产生循 ...
- iOS 循环引用解决方案
一.BLOCK 循环引用 一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身.构成循环引用. // 定义 block 的时候,会对外部变量做一次 cop ...
- iOS 循环引用
1.循环引用一般是指:A持有B,B同时持有A,从而导致死循环无法释放对象. 2.一般循环引用出现在block和delegate中,而一般解决方法就是将self变成weakSelf(强引用变成弱引用), ...
- iOS循环引用问题
今天面试问道了循环引用,所以就看了看,原来只是知道使用了Block容易造成循环引用.今天就来简单的介绍一些循环引用. 先来简单介绍一下什么是循环引用? 循环引用可以简单的理解成:A对象引用了B对象,B ...
- iOS循环引用常见场景和解决办法
好多场景会导致循环引用,例如使用Block.线程.委托.通知.观察者都可能会导致循环引用. 1.委托 遵守一个规则,委托方持有代理方的强引用,代理方持有委托方的弱引用. 实际场景中,委托方会是一个控制 ...
- iOS 循环引用 委托 (实例说明)
如何避免循环引用造成的内存泄漏呢: 以delegate模式为例(viewcontroller和view之间就是代理模式,viewcontroller有view的使用权,viewcontroller同时 ...
- nodejs模块循环引用讲解
CommonJS 模块的重要特性是加载时执行,即脚本代码在require的时候,就会全部执行.一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出. 让我 ...
- 如何在 iOS 中解决循环引用的问题
稍有常识的人都知道在 iOS 开发时,我们经常会遇到循环引用的问题,比如两个强指针相互引用,但是这种简单的情况作为稍有经验的开发者都会轻松地查找出来. 但是遇到下面这样的情况,如果只看其实现代码,也很 ...
- ios 中的循环引用问题及解决
循环引用,指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存.其实有点类似死锁. 举个例子:A->B->C->....->X->B - ...
随机推荐
- 0.Git介绍
版本控制工具:SVN,Git Git是分布式版本控制系统,SVN是集中式的版本控制系统.(借一位网友的图以示区别) SVN只有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过 ...
- javascript 编程风格 部分精要
1 换行保持两个缩进(通常是一行太长) 运算符前后加一个空格,包括赋值运算符和逻辑运算符 括号运算符,左括号之后,右括号之前不应该有空格 段代码无关,添加空行 命名驼峰式,一般首字母小写,其他单词首字 ...
- html笔记第一天
快速生成标签有序ol>li*3无序ul>(li>a{新闻标题})*3定义列表 dl>(dt+dd)*3制作表格table>(tr>td*5)*6pading:3个数 ...
- Index API
Index API 用于在指定索引中添加或更新类型化的JSON文档,使其成为可搜索的. 以下示例将JSON文档插入“twitter”索引中,类型名为“_doc”,ID为1: PUT twitter/_ ...
- ueditor编辑器多图上传为什么顺序打乱了
我上一个版本用的是ueditor1.3.6,自从1.4.2版以后,“前端上传模块统一改用webuploader”,ueditor在多图上传一直考虑漏掉了图片顺序的问题. 我的网站在用户上传图片文章的时 ...
- Vue 学习笔记 — 无法避免的dom操作
简书 使用Vue了一段时间,感觉确实不错,"数据驱动视图"非常好用,大部分情况下都不需要关心dom,但是凡事都有例外,总有一些时候我们必须要直接对dom进行操作,比如下面这个例子: ...
- macOS 10.14虚拟机安装教程
windows10下安装vmware14.0以及macOS 10.14图文详解 工具/原料 windows10 vmware 14.0 macOS 10.14懒人版 mac补丁unlocker工具 ...
- 【从零开始搭建自己的.NET Core Api框架】(三)集成轻量级ORM——SqlSugar:3.1 搭建环境
系列目录 一. 创建项目并集成swagger 1.1 创建 1.2 完善 二. 搭建项目整体架构 三. 集成轻量级ORM框架——SqlSugar 3.1 搭建环境 3.2 实战篇:利用SqlSuga ...
- [Swift]LeetCode915.将分区数组分成不相交的间隔 | Partition Array into Disjoint Intervals
Given an array A, partition it into two (contiguous) subarrays left and right so that: Every element ...
- [Swift]LeetCode1000. 合并石头的最低成本 | Minimum Cost to Merge Stones
There are N piles of stones arranged in a row. The i-th pile has stones[i] stones. A move consists ...