美学原理iOS/OS X内存管理(二):借助工具解决内存问题

达到同首博客iOS/OS
X内存管理(一):基本概念与原理第一谈了iOS/OSX
内存管理着援引计数和内存管理规则,以及引入ARC新的内存管理机制之后怎样抉择ownership
qualifiers(__strong__weak__unsafe_unretained__autoreleasing)来治本内存。这篇我们主要关注于实质上开支被见面遇上哪些内存管理问题,以及哪些运用工具来调节和解决。

当通往生看之前要下载实例MemoryProblems,我们以因为之工程开展如何检查与解决内存问题。

挂指针问题

昂立指针(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相似,被它修饰的变量都不备对象的所有权,但当变量指向的目标的RC为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来检查内存泄露。

静态分析

诚如的话,在次不运行前我们可预先经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大凡匪克稳的,不过可以通过静态分析可得知。

启动Instruments

有时候下静态分析可知检查来有内存泄露问题,但是有时只有运行时使Instruments才会检查到,启动Instruments手续如下

  1. 点击Xcode的食谱栏的 Product -> Profile 启动Instruments

  2. 这,出现Instruments的家伙集,选中Leaks旁工具点击

  3. 打开Leaks工具之后,点击红色圆点按钮启动Leaks工具,在Leaks工具启动以,模拟器或真机也跟着启动

  4. 启动Leaks工具后,它会在程序运行时笔录内存分配信息和检讨是不是生内存泄露。当你点击援循环跻身好页面后,再返回到主页,就会见发出内存泄露

外存泄露.gif

一旦生内存泄露,我们怎么定位乌有和为什么会面出内存泄露?

永恒内存泄露

借助Leaks能十分快定位内存泄露问题,在这例子中,步骤如下:

  • 率先点击Leak Checks日久老红色叉

  • 下一场双击某行内存泄露调用栈,会直接跨越到内存泄露代码位置

分析内存泄露原因

面都定位好内存泄露代码的职位,至于原因是啊?可以翻上一致首的iOS/OS
X内存管理(一):基本概念与原理的轮回引用例子,那里曾起详细的说明。

不便检测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内存泄露是题目,很多基础扎实的同校还知道什么样缓解,不懂得的语,自己查资料解决吧!

总结

相似的话,在创建工程的早晚,我都见面于Build Settings启用Analyze During
‘Build’,每次编译时还见面活动静态分析。这样的话,写了一略带截代码之后,就立马知道是否有内存泄露或另bug问题,并且可以修bugs。而以运作过程遭到,如果出现EXC_BAD_ACCESS,启用NSZombieEnabled,看出现异常后,控制台能否打印出重新多的提示信息。如果想以运作时翻看是否在内存泄露,使用Instrument
Leak工具。但是多少内存泄露是杀为难检查下,有时只有通过手动覆盖dealloc主意,看其最终闹没有发出调用。