笔者前不久终于发布了自己的APP《小印记》,希望读者能前往App Store下载《小印记》支持一下笔者,谢谢!🙂
《小印记》iOS源码分享--极光推送实践篇
《小印记》iOS源码分享--自定义弹框篇
《小印记》源码分享--极光推送服务器篇
《小印记》iOS源码分享--网络层封装篇
前言
当多个对象相互持有形成一个封闭的环时,循环引用问题随之出现,导致内存泄漏。
解决循环引用问题主要有两个办法:
1)自己明确知道这里会存在循环引用,在合理的位置主动断开环中的一个引用(置为nil),使得对象得以回收;
2)使用弱引用。
弱引用的实现原理
弱引用的实现原理是这样,系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。
从这个原理中,我们可以看出,弱引用的使用是有额外的开销的。虽然这个开销很小,但是如果一个地方我们肯定它不需要弱引用的特性,就不应该盲目使用弱引用。举个例子,有人喜欢在手写界面的时候,将所有界面元素都设置成 weak 的,这某种程度上与 Xcode 通过 Storyboard 拖拽生成的新变量是一致的。但是我个人认为这样做并不太合适。因为:
我们在创建这个对象时,需要注意临时使用一个强引用持有它,否则因为 weak 变量并不持有对象,就会造成一个对象刚被创建就销毁掉。
大部分 ViewController 的视图对象的生命周期与 ViewController 本身是一致的,没有必要额外做这个事情。
早先苹果这么设计,是有历史原因的。在早年,当时系统收到 Memory Warning 的时候,ViewController 的 View 会被 unLoad 掉。这个时候,使用 weak 的视图变量是有用的,可以保持这些内存被回收。但是这个设计已经被废弃了,替代方案是将相关视图的 CALayer 对应的 CABackingStore 类型的内存区会被标记成 volatile 类型.
1、delegate与环
//ClassA:
@protocol ClssADelegate <NSObject>
- (void)fuck;
@end
@interface ClassA : UIViewController
@property (nonatomic, strong) id <ClssADelegate> delegate;
@end
//ClassB:
@interface ClassB ()<ClassADelegate>
@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):
@property (nonatomic, weak) id <ClssADelegate> delegate;
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。
网友评论