1. 程式人生 > >ViewController建立後釋放閃退

ViewController建立後釋放閃退

問題描述

在做專案時遇到一個閃退問題,檢視程式碼邏輯發現以下程式碼會造成crash。

- (IBAction)buttonTouchUpInside:(id)sender {
    TestTableViewController *vc = [[TestTableViewController alloc]init];
}

是的,你沒有看錯,上面的程式碼會造成閃退,TestTableViewController的程式碼如下:
TestTableViewController.h檔案

#import <UIKit/UIKit.h>

@interface TestTableViewController
: UITableViewController
@end

TestTableViewController.m檔案

#import "TestTableViewController.h"

@interface TestTableViewController ()

@end

@implementation TestTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    __weak TestTableViewController *weakSelf = self;

    [self.tableView
addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; } - (void)dealloc { [self.tableView removeObserver:self forKeyPath:@"contentOffset"]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id
> *)change context:(void *)context { NSLog(@"%@",change); } @end

出現閃退的程式碼是:

__weak TestTableViewController *weakSelf = self;

我看到這裡的第一感覺是很意外,因為呼叫的地方只是init了一個例項,隨後該例項作為方法中的臨時變數應該就自動釋放了,所以不應該會呼叫到- (void)viewDidLoad方法中的程式碼。
但是,TestTableViewController作為UITableViewController的子類,當在- (void)dealloc方法中,訪問父類的self.tableView時,就觸發了- (void)viewDidLoad方法。
隨後,在執行__weak TestTableViewController *weakSelf = self;程式碼時閃退。

在iOS8.4版本的模擬器上錯誤日誌是EXC_BAD_INSTRUCTION(code=EXC_I386_INVOP,subcode=0x0)
而後我發現該問題在iOS10系統中並不會閃退,而只是在執行時輸出一個warning問題:

[Warning] Attempting to load the view of a view controller while it is deallocating is not allowed and may result in undefined behavior (<TestTableViewController: 0x7fe85fc01790>)

但是,如果呼叫的地方是這樣的:

- (IBAction)buttonTouchUpInside:(id)sender {
    TestTableViewController *vc = [[TestTableViewController alloc]init];
    [self presentViewController:vc animated:YES completion:^{
        [vc dismissViewControllerAnimated:YES completion:^{

        }];
    }];
}

present顯示vc後馬上dismiss掉,這樣並不會出現閃退問題。
也就是說只要TestTableViewController的viewDidLoad方法執行過,那麼在dealloc的時候便不會再觸發viewDidLoad方法。

解決方法

該問題發生有三個條件:
1、在dealloc方法中訪問了父類的tableView屬性。
2、在viewDidLoad方法中定義了weakSelf變數。
3、建立了TestTableViewController的例項變數,但又沒有使用。

以上三個條件,前兩個都是業務邏輯的要求,一般來說無法避免,所以只能儘可能避免第3個條件的出現,即:ViewController的例項,如果用不到就不要建立。
比如:

- (void)showWithType:(NSInteger)type {
    TestTableViewController *vc = [[TestTableViewController alloc]init];
    if (type==1) {
        vc.title = @"1";
    } else if (type==2) {
        vc.title = @"2";
    } else {
        return;
    }
    [self presentViewController:vc animated:YES completion:nil];
}

- (void)showWithType2:(NSInteger)type {
    NSString *title;
    if (type==1) {
        title = @"1";
    } else if (type==2) {
        title = @"2";
    } else {
        return;
    }
    TestTableViewController *vc = [[TestTableViewController alloc]init];
    vc.title = title;
    [self presentViewController:vc animated:YES completion:nil];
}

儘量使用showWithType2方法的寫法,而不是showWithType,以免出現不可預測的閃退問題。

演示程式碼