iOS内存话题: performSelector 导致不立即 dealloc

这次的例子是
1. 基于 iphone 项目.
2. 在模拟器上面测试的.
3. 基于 ARC.

例子比较简单, A ViewController 启动 B ViewController.

主要代码在 B ViewController 里面.

@interface BViewController ()

@property (strong, nonatomic) NSMutableArray *tmpData;

@end

@implementation BViewController

- (void)dealloc
{
    NSLog(@"---------------------------");
    NSLog(@"MyViewController dealloc.");
    NSLog(@"---------------------------");
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _tmpData = [NSMutableArray arrayWithObjects:@"mark.z", nil];

    UIButton *cloneMeBtn = [UIButton buttonWithType:UIButtonTypeCustom];
    [cloneMeBtn setTitle:@"close" forState:UIControlStateNormal];
    cloneMeBtn.backgroundColor = [UIColor blueColor];
    cloneMeBtn.frame = CGRectMake(130, 300, 80, 50);
    [self.view addSubview:cloneMeBtn];
    [cloneMeBtn addTarget:self action:@selector(close) forControlEvents:UIControlEventTouchUpInside];
    
    [self performSelector:@selector(block1) withObject:nil afterDelay:3.0f];
}

- (void)block1
{
    [UIView animateWithDuration:1 animations:^{
        NSLog(@"tmpArray = %@", self.tmpData);
        [self.tmpData addObject:@"hk"];
        NSMutableArray *array = self.tmpData;
        [array addObject:@"ju"];
    } completion:^(BOOL finished) {
        
    }];
}

- (void)block2:(id)sender
{
    NSLog(@"sender = %@", sender);
    MyViewController __weak *weakSelf = sender;
    [UIView animateWithDuration:1 animations:^{
        NSLog(@"tmpArray = %@", weakSelf.tmpData);
        [weakSelf.tmpData addObject:@"hk"];
        NSMutableArray *array = weakSelf.tmpData;
        [array addObject:@"ju"];
    } completion:^(BOOL finished) {
    }];
}

- (void)block3
{
    [UIView animateWithDuration:1 animations:^{
        NSLog(@"tmpArray = %@", _tmpData);
        [_tmpData addObject:@"hk"];
        NSMutableArray *array = _tmpData;
        [array addObject:@"ju"];
        NSLog(@"tmpArray = %@", _tmpData);
    } completion:^(BOOL finished) {
    }];
}

- (void)close
{
    [self dismissViewControllerAnimated:YES completion:^{
        
    }];
}

@end


代码可以看出:
[self performSelector:@selector(block1) withObject:nil afterDelay:3.0f];
调用了 block1.

启动, 并点击 close 按钮, 关闭 B ViewController, 查看打印结果
3s 后打印如下

tmpArray = (
    "mark.z"
)
---------------------------
 MyViewController dealloc.
---------------------------

其实, 我想要的结果是, close 之后立即调用 dealloc, 但是现在需要 3s 后才调用.
试想一下, 如果来回切换并且达到一定次数, 是不是会内存泄露?!
不能忍!

大家可能会想, 是不是因为在 block1里面使用了 self, 造成无法立即释放.
那我们, 调用 block2
[self performSelector:@selector(block2:) withObject:nil afterDelay:3.0f];
看一下结果, 还是3s 后执行

sender = (null)
tmpArray = (null)
---------------------------
MyViewController dealloc.
---------------------------
看来不是, self 的问题.
block2 与 block1不同的就是, 使用了 weak 引用了当前 self(BViewcontroller).
这次, 至少告诉我们, self 已经是 null 了.

在调用一次 block3, 看看有没有新的发现.
[self performSelector:@selector(block3) withObject:nil afterDelay:3.0f];
还是3s 后得到结果

tmpArray = (
    "mark.z"
)
tmpArray = (
    "mark.z",
    hk,
    ju
)
---------------------------
MyViewController dealloc.
---------------------------

好吧, 醉了!
到底罪魁祸首是谁?
答案就是:
performSelector......

在调用
[self performSelector:@selector(block3) withObject:nil afterDelay:3.0f];
我才是对当前对象BViewcontroller有了 stong 的引用, 在 MRC 里面大家可以理解为 retain.
造成, 在 close 时, 无法立即释放自己(dealloc方法没有立即调用).

其实, apple 为我们提供了取消 performSelector 的方法.

+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;


修改 close 方法的代码

- (void)close
{
    [NSObject cancelPreviousPerformRequestsWithTarget:self];
    [self dismissViewControllerAnimated:YES completion:^{
        
    }];
}

再次运行, 可以发现, dealloc 方法立即调用了.

你可能感兴趣的:(iOS)