Fork me on GitHub
An Guoli's Blog

强引用(weak)、弱引用(strong)、dalegate/block/NSTimer中的循环引用、weakSelf、strongSelf

强引用和弱引用:

OC中的内存管理是通过“引用计数器”来实现的。一个对象的生命周期取决于它是否还被其他对象引用(是否retainCount=0)。但在有些情况下,我们并不希望对象的销毁时间由是否被其他对象引用来决定,而是这个对象本该是什么时候销毁就什么时候被销毁。这时,我们得引入“强引用”和“弱引用”的概念。

强引用:

当前对象被其他对象引用时,会执行retain操作,引用计数器+1。当retainCount=0时,该对象才会被销毁。因为我们要进行对象的内存管理,所以这是默认的引用方式。(默认是强引用)

弱引用:

当前对象的生命周期不被是否由其他对象引用限制,它本该什么时候销毁就什么时候被销毁。即使它的引用没断,但是当它的生存周期到了时就会被销毁。

特点:

强引用持有对象;弱引用不持有对象。

强引用可以释放对象,但弱引用不可以,因为弱引用不持有对象,当弱引用指向一个强引用所持有的对象时,当强引用将对象释放掉后,弱引用会自动的被赋值为nil,即弱引用会自动的指向nil。

在强引用中,有时会出现循环引用的情况,这时就需要弱引用来帮忙(__weak)。

理解

当最后一个指向对象的的strong类型的指针离开,这个对象将被释放,如果这个时候还有weak指针指向该对象,则会清除所有剩余的weak指针。

weak指针不持有对象,不影响对象的retainCount。

weak/strong
气球只有被牵着才不会飞走,牵气球的角色有两种:1、弱者-太弱,拉不动,只能在旁边看着,只能作为观察者和使用者,可以有多个。2、强者-拥有气球,负责掌握牵扯气球的线,可以有多个,并且每个都会独自拥有一条牵扯的线。

当所有的线都消失(被剪断)的情况下,气球就会飞走消失。

只有当所有的强者手中的线都间断的时候,才会导致气球消失,而这个时候是不论弱者有多少,都没有任何作用。而所有的弱者都消失时,只要有一个强者还在,那么气球就不会消失。

弱者-weak引用,强者-strong引用

iOS*内存管理-强引用与弱引用

在定义属性时,若声明为retain类型的,则就是强引用;若声明为assign类型的,则就是弱引用

后来内存管理都由ARC来完成后,若是强引用,则就声明为strong;若是弱引用,则就声明为weak

所以说,retain和strong是一致的(声明为强引用)

assign和weak是基本一致的(声明为弱引用)。

之所以说它俩是基本一致是因为它俩还是有所不同的,weak严格的说应当叫“ 归零弱引用 ”,即当对象被销毁后,会自动的把它的指针置为nil,这样可以防止野指针错误。而assign销毁对象后不会把该对象的指针置nil,对象已经被销毁,但指针还在痴痴的指向它,这就成了野指针,这是比较危险的。

weak只能修饰对象类型;assign一般用来修饰基础数据类型(NSInteger,CGFloat等)和C数据类型(int,float,double)等。

todo - 52个方法

unsafe_retain assign

避免“强引用循环“的僵局:

默认的引用方式是强引用,但上面说了有时我们还得使用弱引用,那是什么情况呢?

答案,强引用循环:A对象强引用了B对象,B对象也强引用了A。因为都是强引用,也就是无论是A是B都要在对方的引用断了后才能销毁,但要断了引用,就必须对方对象销毁。就会出现这种僵局,为了避免出现这种情况,就应该有一个对象“示弱”,使其为“弱引用”。

由于要进行内存管理的缘故,OC里的引用默认都是强引用,但为了避免出现”强引用循环僵局“,所以有了弱引用(assign/weak)。

比较常见的,视图中的父子视图之间的引用:父视图强引用子视图,子视图弱引用父视图。

父视图子视图

为什么IBOutlet属性是weak的?

因为当我们将控件拖到Storyboard上,相当于新创建了一个对象,而这个对象是加到视图控制器的view上,view有一个subViews属性,这个属性是一个数组,里面是这个view的所有子view,而我们加的控件就位于这个数组中,那么说明,实际上我们的控件对象是属于view的,也就是说view对加到它上面的控件是强引用。当我们使用Outlet属性的时候,我们是在viewController里面使用,而这个Outlet属性是有view来进行强引用的,我们在viewController里面仅仅是对其使用,并没有必要拥有它,所以是weak的。

以UIButton为例:UIViewController->UIView->UIButton

以UIButton为例

可以改成 strong 吗?

如果将weak改为strong,也是没有问题的,并不会造成强引用循环。当viewController的指针指向其他对象或者为nil,这个viewController销毁,那么对控件就少了一个强引用指针。然后它的view也随之销毁,那么subViews也不存在了,那么控件就又少了一个强引用指针,如果没有其他强引用,那么这个控件也会随之销毁。

控件也会随之销毁

IBOutlet的视图属性为什么被设置成weak?可以改成 strong 吗?

delegate属性为什么用weak修饰?

案例说明:

1
2
案例中的类介绍: 案例共用到三个类,Passenger类(乘客类),Driver类(保姆类),ViewControllr类(控制器类),其中说明问题的关键是之前两个类.
假设,乘客类要想做一些事情(像开车,停车,转弯之类的...)就必须具有代理的属性,因此指定了一个协议<PassengerDelegate>,合情合理.而司机需要成为乘客的代理,必须遵循协议

1.Passenger类 Passenger.h文件

乘客拥有一个代理的属性.并且用修饰词weak修饰的

1
2
3
4
5
6
7
8
9
10
11
// 制定协议
@protocol PassengerDelegate <NSObject>
//这里可以编写代理方法(该案例中用不到所以就不写了)
@end
@interface Passenger : NSObject
/**
* 乘客Passenger的代理属性(这里用的是weak修饰,正确的做法)
*/
@property(nonatomic, weak) id<PassengerDelegate>delegate;
@end

2.Passenger类 Passenger.m文件

当该类的对象被销毁时会调用-dealloc方法(在这个案例中用来观察对象是否被销毁了)

1
2
3
4
5
6
@implementation Passenger
- (void)dealloc
{
NSLog(@"Passenger类被销毁了");
}
@end

3.Driver类 Driver.m文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Driver类也就是司机必须遵循代理协议
@interface Driver()<PassengerDelegate>
//并有一个需要服务的Passenger
@property(nonatomic, strong) Passenger *passenger;
@end
@implementation Driver
//初始化方法
- (instancetype)init
{
self = [super init];
if (self) {
// 初始化'乘客'对象
self.psger = [[Passenger alloc]init];
// 设置'自己(Driver)'为(Passenger)类的代理
self.passenger.delegate = self;
}
return self;
}

4.我们介绍最后一个类 ViewController类

在ViewController的方法ViewDidLoad中创建司机对象,并且该对象作用的范围是这个方法内部.这个方法执行完成,driver对象就会被销毁了.

1
2
3
4
5
6
7
8
9
10
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Driver *driver = [[Driver alloc]init];
}
@end

上面的代码执行完成之后,运行结果如下:

1
2
3
4
5
Driver类被销毁了
Passenger类被销毁了
这就说明Driver对象和Passenger对象在没有用处之后都会被销毁,但是如果用代理用strong修饰,而不是用weak修饰,则不会打印上面的结果!

分析:

delegate-weak

使用weak在程序运行的时候不会造成循环引用,对象都会被顺利的销毁,所以会调用司机类和乘客类的delloc方法

使用strong在程序运行的时候会造成循环引用(reatainCount不为0,只要有强引用,计数器就+1),对象都不会的销毁,所以司机类和乘客类不会调用delloc方法,从而造成了内存泄露的问题

block中的循环引用

题外话:block为什么用copy?

block是一个对象,所以block在创建的时候内存是默认在stack(栈)上的. 而不是在heap(堆)上的.所以他的作用域仅限创建时候的当前上下文(函数,方法等),当在作用域外调用block就会崩溃. Copy可以将block从内存栈区移动到堆区.这样在作用域外也不会崩溃了. 但在ARC下, 使用copy与strong其实都一样, 因为block的retain就是用copy来实现的.block还是建议使用copy修饰.因为MRC下就是就是用copy修饰的.

https://www.jianshu.com/p/9a6e00a54b6c

当A对象里面强引用了B对象,B对象又强引用了A对象,这样两者的retainCount值一直都无法为0,于是内存始终无法释放,导致内存泄露。所谓的内存泄露就是本应该释放的对象,在其生命周期结束之后依旧存在。

当然也存在自身引用自身的,当一个对象内部的一个obj,强引用的自身,也会导致循环引用的问题出现。常见的就是block里面引用的问题。

我们使用block解决循环引用虽然可以控制对象持有时间,在block中还能动态的控制是block变量的值,可以赋值nil,也可以赋值其他的值,但是有一个唯一的缺点就是需要执行一次block才行。否则还是会造成循环引用。

值得注意的是,在ARC下__block会导致对象被retain,有可能导致循环引用。而在MRC下,则不会retain这个对象,也不会导致循环引用.

weakSelf 和 strongSelf

1.weakSelf

__weak __typeof(self)weakSelf = self; 这是AFN里面的写法。。

#define WEAKSELF typeof(self) __weak weakSelf = self; 这是我们平时的写法。。

区分__typeof() 和 typeof(): 其实两者都是一样的东西,只不过是C里面不同的标准,兼容性不同罢了。

1
2
#define WEAKSELF __weak typeof(self)weakSelf = self;
#define WEAKSELF typeof(self) __weak weakSelf = self;

这两种写法就是完全一样的。

我们可以用WEAKSELF来解决block循环引用的问题

假设自定义的一个student类

1
2
3
4
5
6
#import <Foundation/Foundation.h>
typedef void(^Study)();
@interface Student : NSObject
@property (copy , nonatomic) NSString *name;
@property (copy , nonatomic) Study study;
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
student.study = ^{
NSLog(@"my name is = %@",student.name);
};
}

这里肯定出现了循环引用了。student的study的Block里面强引用了student自身。根据深入研究Block捕获外部变量和__block实现原理的分析,可以知道,_NSConcreteMallocBlock(存在堆里)捕获了外部的对象,会在内部持有它。retainCount值会加一。(强引用)

这里形成环的原因block里面持有student本身,student本身又持有block。

如下, 就能解决循环引用的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakSelf = student;
student.study = ^{
NSLog(@"my name is = %@",weakSelf.name);
};
student.study();
}

为什么iOS的Masonry中的self不会循环引用?

1
2
3
4
5
6
7
8
9
10
11
12
UIButton *testButton = [[UIButton alloc] init];
[self.view addSubview:testButton];
testButton.backgroundColor = [UIColor redColor];
[testButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(@100);
make.height.equalTo(@100);
make.left.equalTo(self.view.mas_left);
make.top.equalTo(self.view.mas_top);
}];
[testButton bk_addEventHandler:^(id sender) {
[self dismissViewControllerAnimated:YES completion:nil];
} forControlEvents:UIControlEventTouchUpInside];

看到这里,读者应该就应该能回答这个问题了。

1
2
3
4
5
6
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}

关于 Masonry ,它内部会引用变量 self,然后对其执行了setTranslatesAutoresizingMaskIntoConstraints:方法, 进入block的是self.view,闭包虽然引用了self,但是self并没有引用这个闭包,执行完毕后,block会被销毁,没有形成环, 所以,没有引起循环依赖。

2.strongSelf

如果block里面不加strong typeof(weakSelf)strongSelf = weakSelf会如何呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakSelf = student;
student.study = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@",weakSelf.name);
});
};
student.study();
}

输出:

1
my name is = (null)

重点就在dispatch_after这个函数里面。在study()的block结束之后,student被自动释放了。又由于dispatch_after里面捕获的 weak 的 student ,根据第二章讲过的weak的实现原理,在原对象释放之后,weak对象就会变成null(对应的weak对象就会被指为nil),防止野指针。所以就输出了null了。

那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?

究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加__strong。

在block里面使用的__strong修饰的weakSelf是为了在函数生命周期中防止self提前释放。strongSelf是一个自动变量当block执行完毕就会释放自动变量strongSelf不会对self进行一直进行强引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#import "ViewController.h"
#import "Student.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Student *student = [[Student alloc]init];
student.name = @"Hello World";
__weak typeof(student) weakSelf = student;
student.study = ^{
__strong typeof(student) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"my name is = %@",strongSelf.name);
});
};
student.study();
}

输出

1
my name is = Hello World

weakSelf、strongSelf的用途

weakSelf 是为了block不持有self,避免Retain Circle循环引用。在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。

strongSelf的目的是因为一旦进入block执行,假设不允许self在这个执行过程中释放,就需要加入strongSelf。block执行完后这个 strongSelf 会自动释放,没有不会存在循环引用问题。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。

关于Retain Circle最后总结一下,有3种方式可以解决循环引用。

《Effective Objective-C 2.0》(编写高质量iOS与OS X代码的52个有效方法)有介绍

@weakify、@strongify实现原理

@weakify、@strongify,这两个关键字是RAC中避免Block循环引用而开发的2个宏

@weakify、@strongify的作用和weakSelf、strongSelf对应的一样。

总结一下

1
2
3
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;

经过分析以后,其实@weakify(self) 和 @strongify(self) 就是比我们日常写的weakSelf、strongSelf多了一个@autoreleasepool{}而已

深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用

NSTimer中的循环引用

iOS--强引用,弱引用 及strong, weak,retain,copy,assign的关系

关于iOS的强引用,弱引用及strong,retain,copy,weak,assign的关系