iOS/OS X内存管理(二):借助工具化解内存难点

浅析内存败露原因

地点已经稳定好内存败露代码的岗位,至于原因是怎么着?能够查看上一篇的iOS/OS
X内存管理(一):基本概念与原理
的轮回引用例子,那里已经有详尽的解释。

启动Instruments

偶然利用静态分析可见检查出部分内存败露难点,不过有时只有运行时利用Instruments才能检查到,运维Instruments手续如下

  1. 点击Xcode的菜单栏的 Product -> Profile 启动Instruments

  2. 那时,出现Instruments的工具集,选中Leaks子工具点击

  3. 打开Leaks工具之后,点击血牙红圆点按钮运维Leaks工具,在Leaks工具运维同时,模拟器或真机也跟着运维

  4. 启动Leaks工具后,它会在程序运营时记录内存分配信息和检查是或不是爆发内存败露。当你点击引用循环跻身那多少个页面后,再回去到主页,就会生出内存走漏

内存败露.gif

纵然暴发内存走漏,大家怎么定位何地暴发和为什么会暴发内存败露?

永恒内存走漏

借助Leaks能很快定位内存走漏难题,在这些例子中,步骤如下:

  • 率先点击Leak Checks岁月条拾壹分金黄叉

  • 接下来双击某行内存败露调用栈,会直接跳到内存走漏代码地点

总结

诚如的话,在成立工程的时候,小编都会在Build Settings启用Analyze During
‘Build’,每一回编译时都会自动静态分析。那样的话,写完一小段代码之后,就霎时知道是还是不是留存内存走漏或任何bug难题,并且可以修bugs。而在运营进度中,要是出现EXC_BAD_ACCESS,启用NSZombieEnabled,看出现卓殊后,控制台能依然不能打印出越来越多的指示音信。倘若想在运作时翻看是不是留存内存走漏,使用Instrument
Leak工具。不过有个别内存败露是很难检查出来,有时只有由此手动覆盖dealloc措施,看它说到底有没有调用。

昂立指针难点

昂立指针(Dangling
Pointer
)就是当指针指向的靶子已经释放或回收后,但平素不对指针做别的改动(一般的话,将它指向空指针),而是依旧指向原来早就回收的地方。若是指针指向的目的已经出狱,但照样采纳,那么就会促成程序crash。

当您运转MemoryProblems后,点击昂立指针不行选项,就会面世EXC_BAD_ACCESS崩溃新闻

大家看看那么些NameListViewController是做哪些的?它延续UITableViewController,首要体现七个名字的音信。它的贯彻文件如下:

static NSString *const kNameCellIdentifier = @"NameCell";

@interface NameListViewController ()

#pragma mark - Model
@property (strong, nonatomic) NSArray *nameList;

#pragma mark - Data source
@property (assign, nonatomic) ArrayDataSource *dataSource;

@end

@implementation NameListViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.tableView.dataSource = self.dataSource;
}

#pragma mark - Lazy initialization
- (NSArray *)nameList
{
    if (!_nameList) {
        _nameList = @[@"Sam", @"Mike", @"John", @"Paul", @"Jason"];
    }
    return _nameList;
}

- (ArrayDataSource *)dataSource
{
    if (!_dataSource) {
        _dataSource = [[ArrayDataSource alloc] initWithItems:self.nameList
                                              cellIdentifier:kNameCellIdentifier
                                              tableViewStyle:UITableViewCellStyleDefault
                                          configureCellBlock:^(UITableViewCell *cell, NSString *item, NSIndexPath *indexPath) {
            cell.textLabel.text = item;
        }];
    }
    return _dataSource;
}

@end

要想经过tableView展现数据,首先要兑现UITableViewDataSource那些协议,为了瘦身controller和复用data
source,小编将它分离到二个类ArrayDataSource来实现UITableViewDataSource本条协议。然后在viewDidLoad措施里面将dataSource赋值给tableView.dataSource

解释完NameListViewController的义务后,接下去大家需求思考出现EXC_BAD_ACCESS错误的由来和岗位信息。

相似的话,出现EXC_BAD_ACCESS错误的缘由都以悬挂指针致使的,但具体是哪些指针是悬挂指针还不明确,因为控制台并没有交给具体crash消息。

启用NSZombieEnabled

要想赢得更加多的crash新闻,你需求运转NSZombieEnabled。具体步骤如下:

  1. 选中Edit Scheme,并点击

  2. Run -> Diagnostics -> Enable Zombie Objects

设置完将来,再一次运维和点击悬挂指针,尽管会再也crash,但本次控制台打印了以下有用新闻:

信息message sent to deallocated instance 0x7fe19b081760马虎是向三个已放出对象发送音信,相当于已释放对象还调用有个别方法。今后大家大体知道什么来头造成程序会crash,不过具体哪些目的被放出还还是使用呢?

点击下边铁锈色框的Continue program execution按钮继续运维,截图如下:

瞩目上边的多少个栗色框,它们八个地方是同等,而且ArrayDataSource前边有个_NSZombie_修饰符,说明dataSource对象被放走还依旧拔取。

再进一步看dataSource宣称属性的修饰符是assign

#pragma mark - Data source
@property (assign, nonatomic) ArrayDataSource *dataSource;

assign对应就是__unsafe_unretained,它跟__weak相似,被它修饰的变量都不富有对象的全部权,但当变量指向的靶子的奔驰M级C为0时,变量并不设置为nil,而是继续封存对象的地址。

因此,在viewDidLoad方法中

- (void)viewDidLoad {
    [super viewDidLoad];

    self.tableView.dataSource = self.dataSource;    
    /*  由于dataSource是被assign修饰,self.dataSource赋值后,它对象的对象就马上释放,
     *  而self.tableView.dataSource也不是strong,而是weak,此时仍然使用,所有会导致程序crash
     */
}

浅析完原委和稳定错误代码后,至于怎么修改,作者想我们都心知肚明了,借使还不通晓的话,留言给自己。

内存走漏难题

还记得上一篇iOS/OS
X内存管理(一):基本概念与原理
的引用循环例子吗?它会导致内存败露,上次只是文字描述,不怎么直观,这一次我们品尝使用Instruments里头的子工具Leaks来检查内存败露。

上一篇博客iOS/OS
X内存管理(一):基本概念与原理
重大讲了iOS/OSX
内存管理中援引计数和内存管理规则,以及引入ARAV4C新的内存管理机制之后什么选拔ownership
qualifiers(__strong__weak__unsafe_unretained__autoreleasing)来治本内存。那篇我们最紧要关怀在其实开支中会遇到什么内存管理难点,以及哪些使用工具来调节和平化解决。

在往下看以前请下载实例MemoryProblems,大家将以这几个工程进行怎样检查和化解内存难题。

不便检测Block引用循环

绝半数以上的内存难题都得以通过静态分析和Instrument
Leak工具检测出来,可是有种block引用循环是麻烦检测的,看我们这一个Block内存败露事例,跟上边的悬挂指针事例几乎,只是在configureCellBlock内部调用二个艺术configureCell

- (ArrayDataSource *)dataSource
{
    if (!_dataSource) {
        _dataSource = [[ArrayDataSource alloc] initWithItems:self.nameList
                                              cellIdentifier:kNameCellIdentifier
                                              tableViewStyle:UITableViewCellStyleDefault
                                          configureCellBlock:^(UITableViewCell *cell, NSString *item, NSIndexPath *indexPath) {
                                              cell.textLabel.text = item;

                                              [self configureCell];
                                          }];
    }
    return _dataSource;
}

- (void)configureCell
{
    NSLog(@"Just for test");
}

- (void)dealloc
{
    NSLog(@"release BlockLeakViewController");
}

小编们第叁用静态分析来看望能无法检查出内存败露:

结果是没有任何内存走漏的唤醒,大家再用Instrument
Leak工具在运作时看看能否够检查出:

结果跟使用静态分析一样,依然没有其余内存败露新闻的唤起。

那就是说大家怎么知道这一个BlockLeakViewController发生了内存败露呢?如故依据iOS/OS
X内存管理机制的三个基本原理:当有个别对象的引用计数为0时,它就会自动调用- (void)dealloc方法。

在这一个例子中,如果BlockLeakViewController被navigationController
pop出去后,没有调用dealloc主意,表明它的有个别属性对象依然被全部,未被放出。而自小编在dealloc方式打印release
BlockLeakViewController
信息:

- (void)dealloc
{
    NSLog(@"release BlockLeakViewController");
}

在自个儿点击重临按钮后,其并从未打印出来,由此这几个BlockLeakViewController存在内存走漏难点的。至于什么缓解block内存走漏这一个难题,很多基础扎实的校友都知道如何消除,不懂的话,本身查资料化解吧!

静态分析

貌似的话,在程序未运维此前我们得以先经过Clang Static
Analyzer
(静态分析)来检查代码是还是不是留存bug。比如,内存走漏、文件能源败露或访问空指针的数额等。上面有个静态分析的事例来叙述如何启用静态分析以及静态分析可以寻找哪些bugs。

运行程序后,点击静态分析,登时就出现crash

此时,纵然启用NSZombieEnabled,控制台也不可以打印出更加多关于bug的音信,具体原因是怎么,等下会解释。

打开StaticAnalysisViewController,里面引用Facebook
Infer
工具的代码例子,包括个人经常支出中会出现的bugs:

@implementation StaticAnalysisViewController

#pragma mark - Lifecycle
- (void)viewDidLoad
{
    [super viewDidLoad];

    [self memoryLeakBug];
    [self resoureLeakBug];
    [self parameterNotNullCheckedBlockBug:nil];
    [self npeInArrayLiteralBug];
    [self prematureNilTerminationArgumentBug];
}

#pragma mark - Test methods from facebook infer iOS Hello examples
- (void)memoryLeakBug
{
     CGPathRef shadowPath = CGPathCreateWithRect(self.inputView.bounds, NULL);
}

- (void)resoureLeakBug
{
    FILE *fp;
    fp=fopen("info.plist", "r");
}

-(void) parameterNotNullCheckedBlockBug:(void (^)())callback {
    callback();
}

-(NSArray*) npeInArrayLiteralBug {
    NSString *str = nil;
    return @[@"horse", str, @"dolphin"];
}

-(NSArray*) prematureNilTerminationArgumentBug {
    NSString *str = nil;
    return [NSArray arrayWithObjects: @"horse", str, @"dolphin", nil];
}

@end

上面大家经过静态分析来检查代码是还是不是存在bugs。有八个点子:

  • 手动静态分析:每趟都以经过点击菜单栏的Product ->
    Analyze或快捷键shift + command + b

  • 机动静态分析:在Build Settings启用Analyze During
    ‘Build’
    ,每一遍编译时都会活动静态分析

静态分析结果如下:

通过静态分析结果,大家来分析一下为啥NSZombieEnabled不可以定点EXC_BAD_ACCESS的错误代码地点。由于callback传入进来的是null指针,而NSZombieEnabled唯其如此针对某些已经出狱对象的地方,所以运行NSZombieEnabled是不大概固定的,不过可以经过静态分析可获悉。