iOSメモリのトピック:performSelectorによる即時deallocの発生

4235 ワード

今回の例は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];
block 1が呼び出されました.
起動して、closeボタンをクリックして、B ViewControllerを閉じて、印刷結果を表示します.
3 s後に次のように印刷します.
tmpArray = (
    "mark.z"
)
---------------------------
 MyViewController dealloc.
---------------------------

実は、私が望んでいる結果は、closeの後ですぐにdeallocを呼び出しますが、今は3 s後に呼び出す必要があります.
何度も切り替えて一定回数に達したら、メモリが漏れるのではないかと考えてみましょう.
我慢できない!
block 1でselfを使ったため、すぐに解放できないのではないかと思うかもしれません.
では、block 2を呼び出します.
[self performSelector:@selector(block2:) withObject:nil afterDelay:3.0f];
結果を見て、やはり3 s後に実行します
sender = (null)
tmpArray = (null)
---------------------------
MyViewController dealloc.
---------------------------
いいえselfの問題です.
block 2がblock 1と異なるのは、weakを用いて現在のself(BViewcontroller)を参照することである.
今回は、少なくともselfはnullだと教えてくれました.
block 3を呼び出して、新しい発見があるかどうか見てみましょう.
[self performSelector:@selector(block3) withObject:nil afterDelay:3.0f];
やはり3 s後に結果が得られます
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メソッドが直ちに呼び出されたことがわかります.