Skip to content

39题关于系统的某些block api的循环引用解释有问题 #73

Closed
@agger0207

Description

@agger0207

前面有好几个人已经提到了:

[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" 
                                                  object:nil 
                           queue:[NSOperationQueue mainQueue]
                                              usingBlock:^(NSNotification * notification) {
                                                    self.someProperty = xyz; }]; 

存在内存泄漏;

而下面的代码并不存在内存泄漏:

dispatch_group_async(self.operationGroup, self.serialQueue, ^{
    [self doSomething];
});

个人理解:
首先下面这段话是没有问题的

   “所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题”

那么为什么第一段NSNotificationCenter的代码会有内存泄漏问题呢?其实和循环引用没有关系;这里block强引用了self, 但是self并没有强引用block; 所以没有循环引用。

这里出现内存泄漏问题实际上是因为,[NSNoficationCenter defaultCenter]持有了block, 这个block持有了self; 而[NSNoficationCenter defaultCenter]是一个单例,因此这个单例持有了self, 从而导致self不被释放。以下来自API文档:
The block is copied by the notification center and (the copy) held until the observer registration is removed.

但整个过程中并没有循环引用,因为self没有持有NotificationCenter, 也没有持有block。即使self持有这个Observer, 并没有任何证据或者文档标明Observer会持有这个block, 所以作者的解释是不正确的。而我个人的理解,这里Observer应该是不持有block的,因为只需要NSNotificationCenter同时持有Observer和block即可实现API所提供的功能, 这里也不存在循环引用。

针对第二段GCD的问题,实际上,self确实持有了queue; 而block也确实持有了self; 但是并没有证据或者文档表明这个queue一定会持有block; 而且即使queue持有了block, 在block执行完毕的时候,由于需要将任务从队列中移除,因此完全可以解除queue对block的持有关系,所以实际上这里也不存在循环引用。下面的测试代码可以验证这一点(其中HTMemoryDetecter有一个属性name):

    HTMemoryDetecter *detector = [[HTMemoryDetecter alloc] init];
dispatch_group_async(self.operationGroup, self.serialQueue, ^{
    NSLog(@"dispatch_async demoGCDRetainCycle");
    [self.testList addObject:@"demoGCDRetainCycle2"];
    detector.name = @"测试";
    NSLog(@"Detecor 's name: %@", detector.name);
});

那么会看到先打印出dispatch_async demoGCDRetainCycle, 然后打印出这个detecotr的name, 然后执行HTMemoryDetecter的dealloc方法。也就是说在这个block执行完毕的时候,仅由这个block持有的detector就会被释放了, 从而验证这个block都被释放了,即使对应的queue还存在。

什么时候这里会有循环引用呢?仍然是当self持有block的时候,例如这个block是self的一个strong的属性,但这就和GCD的调用无关了,这个时候无论是否调用GCD的API都会有循环引用的。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @ChenYilong@agger0207@liftliftlift

        Issue actions

          39题关于系统的某些block api的循环引用解释有问题 · Issue #73 · ChenYilong/iOSInterviewQuestions