美文网首页iOS 开发 Objective-C
iOS 底层 day29 循环引用 和 内存泄露

iOS 底层 day29 循环引用 和 内存泄露

作者: 望穿秋水小作坊 | 来源:发表于2020-10-14 14:29 被阅读0次

    一、UIView 的 block 写动画

    1. 请问下面代码有内存泄露吗?有循环引用吗?
    // 情况一
    @implementation UIViewAnimationsBlock
    - (void)viewDidLoad {
        [super viewDidLoad];
        NSTimeInterval duration = 1000;
        [UIView animateWithDuration:duration animations:^{
            [self.view.superview layoutIfNeeded];
        }];
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    • 没有内存泄露,也没有循环引用
    2. UIViewblock 写动画时不需要考虑循环引用的原因是?
    • block 会立即执行,所以并不会持有 block。其中 duration 延迟时间并不能决定 block 执行的时机,block 始终是瞬间执行的。
    • 这里涉及了 CoreAnimation (核心动画)相关的知识:UIView 层Layer 层data 数据层
    • UIView 层block 仅仅是提供了类似快照 data 的变化。
    • 当真正执行 Animation 动画时才会将 "原有状态""执行完 block 的状态" 做一个差值,来去做动画。

    二、NSNotificationCenter 的 block 使用

    1. 请问下面代码有内存泄露吗?有循环引用吗?
    // 情况二
    @implementation NSNotificationCenterBlock
    - (void)viewDidLoad {
        [super viewDidLoad];
        [[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
                               object:nil
                               queue:[NSOperationQueue mainQueue]
                           usingBlock:^(NSNotification * notification) {
            NSLog(@"%@", self);
        }];
    }
    - (void)remove {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    - (void)dealloc {
        [self remove];
        NSLog(@"%s", __func__);
    }
    @end
    
    • 内存泄露,没有循环引用
    2. 情况二解析
    • [NSNotificationCenter defaultCenter] 是一个单例,它需要在接到通知时候调用 block,所以[NSNotificationCenter defaultCenter] 需要持有 block,会对 block 调用 -copy 方法。
    • [NSNotificationCenter defaultCenter] 持有 blockblock 持有 self[NSNotificationCenter defaultCenter] 不会释放,因此 self 也不会得到释放,也不会调用 -dealloc 方法。
    • 所以不存在循环引用,但是存在内存泄露
    3. 请问下面代码有内存泄露吗?有循环引用吗?
    // 情况三
    @interface NSNotificationCenterIVARBlock ()
    @property (nonatomic, strong) id observer;
    @end
    @implementation NSNotificationCenterIVARBlock
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
                                                                             object:nil
                                                                              queue:nil
                                                                         usingBlock:^(NSNotification *note) {
                                                                             NSLog(@"%@", self);
        }];
    }
    - (void)remove {
        [[NSNotificationCenter defaultCenter] removeObserver:self.observer];
    }
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        [self remove];
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    • 存在循环引用,也存在内存泄露
    4. 情况三解析
    • self 持有 observerobserver 持有 blockblock 指持有 self;这就造成了循环引用。
    • 虽然 -remove方法调用observer[NSNotificationCenter defaultCenter] 移除,但是不能改变已经形成的循环引用。
    5. 如何利用 Xcode 检查情况三存在的内存泄露和循环引用?
    • 方法一:可用 Xcode-instruments-Leak 工具查看
    • 打开方法 Product → profile → leaks
    leaks 查看NSNotificationCenterIVARBlock 内存泄露图
    • 方法二:借助 Xcode 的 Debug Memory Graph 功能查看
      Xcode 的 `Debug Memory Graph`开启
    循环引用结构

    三、GCD 和 NSOperationQueue 的 block 使用

    1. 请问下面代码有内存泄露吗?有循环引用吗?
    // 情况四
    @interface GCDBlock ()
    @property(nonatomic, strong) dispatch_queue_t myQueue;
    @end
    
    @implementation GCDBlock
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.myQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_async(self.myQueue, ^{
            [self doSomething];
        });
    }
    - (void)doSomething {
        NSLog(@"%s", __func__);
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    • 没有内存泄露,也没有循环引用
    2. 请问下面代码有内存泄露吗?有循环引用吗?
    // 情况五
    @implementation NSOperationQueueBlock
    - (void)viewDidLoad {
        [super viewDidLoad];
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@"%@",self); }];
    }
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    @end
    
    • 没有内存泄露,也没有循环引用
    3. 情况四 情况五解析
    • 针对 GCDNSOperationQueue 的问题,我们只需记住,block 执行完毕之后,block 将会被释放掉。从而破除了循环引用和内存泄露

    相关文章

      网友评论

        本文标题:iOS 底层 day29 循环引用 和 内存泄露

        本文链接:https://www.haomeiwen.com/subject/qmuipktx.html