iOS block循环引用详解及常见误区

2025-05-29 0 86

Block循环引用

什么情况下block会造成循环引用

ARC 情况下 block为了保证代码块内部对象不被提前释放,会对block中的对象进行强引用,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,就会造成循环引用

常见误区

误区一.所有block都会造成循环引用

在block中,并不是所有的block都会循造成环引用,比如UIView动画block、Masonry添加约束block、AFN网络请求回调block等。    

1. UIView动画block不会造成循环引用是因为这是类方法,不可能强引用一个类,所以不会造成循环引用。    

2. Masonry约束block不会造成循环引用是因为self并没有持有block,所以我们使用Masonry的时候不需要担心循环引用

Masonry内部代码

?

1

2

3

4

5

6

7
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {

self.translatesAutoresizingMaskIntoConstraints = NO;

MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];

//这里并不是self.block (self并没有持有block 所以不会引起循环引用)

block(constraintMaker);

return [constraintMaker install];

}

3.AFN请求回调block不会造成循环引用是因为在内部做了处理。
block先是被AFURLSessionManagerTaskDelegate对象持有。而AFURLSessionManagerTaskDelegate对象被mutableTaskDelegatesKeyedByTaskIdentifier字典持有,在block执行完成后,mutableTaskDelegatesKeyedByTaskIdentifier字典会移除AFURLSessionManagerTaskDelegate对象,这样对象就被释放了,所以不会造成循环引用

AFN内部代码

?

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

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59
#pragma mark - 添加代理

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask

uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock

downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock

completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler

{

AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];

delegate.manager = self;

//block被代理引用

delegate.completionHandler = completionHandler;

dataTask.taskDescription = self.taskDescriptionForSessionTasks;

//设置代理

[self setDelegate:delegate forTask:dataTask];

delegate.uploadProgressBlock = uploadProgressBlock;

delegate.downloadProgressBlock = downloadProgressBlock;

}

#pragma mark - 设置代理

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate

forTask:(NSURLSessionTask *)task

{

NSParameterAssert(task);

NSParameterAssert(delegate);

[self.lock lock];

//代理被mutableTaskDelegatesKeyedByTaskIdentifier字典引用

self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;

[delegate setupProgressForTask:task];

[self addNotificationObserverForTask:task];

[self.lock unlock];

}

#pragma mark - 任务完成

- (void)URLSession:(NSURLSession *)session

dataTask:(NSURLSessionDataTask *)dataTask

didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask

{

AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];

if (delegate) {

//任务完成,移除

[self removeDelegateForTask:dataTask];

[self setDelegate:delegate forTask:downloadTask];

}

if (self.dataTaskDidBecomeDownloadTask) {

self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);

}

}

#pragma mark - 移除任务代理

- (void)removeDelegateForTask:(NSURLSessionTask *)task {

NSParameterAssert(task);

AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

[self.lock lock];

[delegate cleanUpProgressForTask:task];

[self removeNotificationObserverForTask:task];

//移除

[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];

[self.lock unlock];

}

误区二.block中只有self会造成循环引用

在block中并不只是self会造成循环引用,用下划线调用属性(如_name)也会出现循环引用,效果和使用self是一样的(内部会用self->name去查找)。

?

1

2

3

4

5

6

7

8
//会造成循环引用

_person1 = [[Person alloc] init];

_person2 = [[Person alloc] init];

_person2.name = @"张三";

[_person1 Block:^{

NSLog(@"%@",_person2.name)

}];

误区三.通过__weak __typeof(self) weakSelf = self;可以解决所有block造成的循环引用

大部分情况下,这样使用是可以解决block循环引用,但是有些情况下这样使用会造成一些问题,比如在block中延迟执行一些代码,在还没有执行的时候,控制器被销毁了,这样控制器中的对象也会被释放,__weak对象就会变成null。所以会输出null。

?

1

2

3

4

5

6

7

8

9

10
//在延迟执行期间,控制器被释放了,打印出来的会是**(null)**

_person1 = [[Person alloc] init];

_person2 = [[Person alloc] init];

_person2.name = @"张三";

__weak __typeof(self) weakSelf = self;

[_person1 Block:^{

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

NSLog(@"%@",weakSelf.person2.name);

});

}];

误区四.用self调用带有block的方法会引起循环引用

并不是所有通过self调用带有block的方法会引起循环引用,需要看方法内部有没有持有self。

?

1

2

3

4
//不会引起循环引用

[self dismissViewControllerAnimated:YES completion:^{

NSLog(@"%@",self.string);

}];

如何避免循环引用

方式一、weakSelf、strongSelf结合使用

使用weakSelf结合strongSelf的情况下,能够避免循环引用,也不会造成提前释放导致block内部代码无效。

?

1

2

3

4

5

6

7

8

9

10
_person1 = [[Person alloc] init];

_person2 = [[Person alloc] init];

_person2.name = @"张三";

__weak __typeof(self) weakSelf = self;

[_person1 Block:^{

__typeof(&*weakSelf) strongSelf = weakSelf;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

NSLog(@"%@",strongSelf.person2.name);

});

}];

方式二、block的外部对象使用week

外部对象通过week修饰,使用全局弱指针指向一个局部强引用对象,这样局部变量在超出其作用域后也不会被销毁,因为是弱指针,所以不会造成循环引用

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24
@interface CLViewController ()

//弱引用指针

@property (nonatomic,weak) Person *person1;

@property (nonatomic,strong) Person *person2;

@end

@implementation CLViewController

- (void)viewDidLoad {

[super viewDidLoad];

self.view.backgroundColor = [UIColor redColor];

//局部强引用对象

Person *person1 = [[Person alloc] init];

_person1 = person1;

_person2 = [[Person alloc] init];

_person2.name = @"张三";

[_person1 Block:^{

NSLog(@"%@",self.person2.name);

}];

}

方式三.将对象置为nil

使用完对象之后就没有必要再保留该对象了,将对象置为nil。在ARC中,被置为nil的对象会被销毁。虽然这样也可以达到破除循环引用的效果,但是这样使用起来很不方便,如果一个对象有多个block,在前面将对象置空,后面的block就不会执行,所以使用这种方法来防止循环引用,需要注意在合适的位置将对象置空。

?

1

2

3

4

5

6

7

8

9

10

11

12
_person1 = [[Person alloc] init];

_person2 = [[Person alloc] init];

_person2.name = @"张三";

[_person1 Block:^{

NSLog(@"%@",self.person2.name);

//置空,避免循环引用

_person1 = nil;

}];

//由于上面已经将对象置空,所以这里block里边的代码不会执行

[_person1 Block:^{

NSLog(@"%@",self.person2.name);

}];

虽然这种方式使用起来不是很友好,但是在封装block的时候,可以考虑使用完马上置空当前使用的block,这样使用的时候就不需要考虑循环引用的问题。

?

1

2

3

4

5

6

7

8

9
- (void)back

{

if (self.BackBlock)

{

self.BackBlock(button);

}

//使用完,马上置空当前block

self.BackBlock = nil;

}

总结

使用block的时候,我们首先需要做的就是判断当前block是否会引起循环引用,如果会引起循环引用,再考虑采取哪种方式来避免循环引用

到此这篇关于iOS block循环引用详解和应用的文章就介绍到这了,更多相关iOS block循环引用内容请搜索快网idc以前的文章或继续浏览下面的相关文章希望大家以后多多支持快网idc!

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

快网idc优惠网 建站教程 iOS block循环引用详解及常见误区 https://www.kuaiidc.com/89252.html

相关文章

发表评论
暂无评论