内存管理概念与原理以及解决办法
一、内存管理的基本规则在Objective-C的内存管理中,其实就是引用计数(reference count)的管理。内存管理就是在程序需要时程序员分配一段内存空间,而当使用完之后将它释放。如果程序员对内存资源使用不当,有时不仅会造成内存资源浪费,甚至会导致程序crach。
文章图片
image
二、 引用计数(Reference Count)为了解释引用计数,我们做一个类比:员工在办公室使用灯的情景。
文章图片
image
从底层的实现来讲就是一个
- 当第一个人进入办公室时,他需要使用灯,于是开灯,引用计数为1
- 当另一个人进入办公室时,他也需要灯,引用计数为2;每当多一个人进入办公室时,引用计数加1
- 当有一个人离开办公室时,引用计数减1,当引用计数为0时,也就是最后一个人离开办公室时,他不再需要使用灯,关灯离开办公室。
class结构体
,结构体内部有个值(retain count
)记录了对象拥有者(ownship
)的个数,当计数值为0时,系统将自动释放这个对象占用的内存空间。文章图片
image
三、内存管理基本规则从上面灯的例子,我们对比一下灯的动作与Objective-C对象的动作有什么相似之处:
灯的动作 | Objective-C对象的动作 |
---|---|
开灯 | 创建一个对象并获取它的所有权(ownership) |
使用灯 | 获取对象的所有权 |
不使用灯 | 放弃对象的所有权 |
关灯 | 释放对象 |
文章图片
image
- 而Objective-C对象的动作对应有哪些方法以及这些方法对引用计数有什么影响?
Objective-C对象的动作 | Objective-C对象的方法 |
---|---|
1. 创建一个对象并获取它的所有权 | alloc/new/copy/mutableCopy (RC = 1) |
2. 获取对象的所有权 | retain (RC + 1) |
3. 放弃对象的所有权 | release (RC - 1) |
4. 释放对象 | dealloc (RC = 0 ,此时会调用该方法) |
alloc
一个对象objc
,此时RC=1
;在某个地方你又retain
这个对象objc
,此时RC
加1,也就是RC=2
;由于调用alloc/retain
一次,对应需要调用release
一次来释放对象objc
,所以你需要release
对象objc
两次,此时RC=0
;而当RC=0
时,系统会自动调用dealloc
方法释放对象。除了
alloc/new/copy/mutableCopy/retain
这几种方法可以获取对象的所有权(ownship)外,当对象被添加到集合对象(array, dictionary, set)中时,集合对象会获取集合中所有对象的所有权(RC+1
),当集合对象释放时,也会默认向集合中所有对象发送release
消息(RC -1)
,符合谁创建谁释放
的原则。文章图片
image
- 注意下以下情况是不会获取对象的所有权:
(1)不使用alloc/new/copy/mutableCopy
方法引用的对象将不会获取对象的拥有权
- (NSString *)fullName {
NSString *string = [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
return string;
}
这个函数调用的是
NSString
的 stringWithFormat
并不不满足内存管理基本原则,不会拥有对象的所有权,所以可以放心的返回。而不用调用release方法或者autorelease方法。(2)引用对象
指针地址
的方式(指针(*)、取地址(&)
),不会获取对象的拥有权。这个比较好理解,比如我们常见的error。
NSString *fileName = <#Get a file name#>;
NSError *error;
NSString *string = [[NSString alloc] initWithContentsOfFile:fileName
encoding:NSUTF8StringEncoding error:&error];
if (string == nil) {
// Deal with error...
}
// ...
[string release];
四、 Autorelease Pool在开发中,我们常常都会使用到
局部变量
,局部变量一个特点就是当它超过作用域时,就会自动释放。而autorelease pool
跟局部变量
类似,当执行代码超过autorelease pool
块时,所有放在autorelease pool
的对象都会自动调用release
。它的工作原理如下:- 创建一个
NSAutoreleasePool
对象 - 在autorelease pool块的对象调用
autorelease
方法 - 释放
NSAutoreleasePool
对象
文章图片
image iOS 5/OS X Lion前的(等下会介绍引入ARC的写法)实例代码如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/* 超过autorelease pool作用域范围时,obj会自动调用release方法 */
由于放在autorelease pool的对象并不会马上释放,如果有大量图片数据放在这里的话,将会导致内存不足。
for (int i = 0;
i < numberOfImages;
i++)
{
/*处理图片,例如加载
*太多autoreleased objects存在
*由于NSAutoreleasePool对象没有被释放
*在某个时刻,会导致内存不足
*/
}
文章图片
image 像上面这种情况你就可以这么写:
for (int i = 0;
i < numberOfImages;
i++)
{
@autoreleasepool {
/*
*这样临时的autoreleased objects就会在autoreleasepool 结束时释放达到最少的内存占用。
*/
}
}
五、ARC管理方法iOS/OS X内存管理方法有两种:手动引用计数(
Manual Reference Counting
)和自动引用计数(Automatic Reference Counting
)。从OS X Lion和iOS 5开始,不再需要程序员手动调用retain
和release
方法来管理Objective-C
对象的内存,而是引入一种新的内存管理机制Automatic Reference Counting(ARC)
,简单来说,它让编译器
来代替程序员来自动加入retain
和release
方法来持有
和放弃
对象的所有权。文章图片
image 在ARC内存管理机制中,id和其他对象类型变量必须是以下四个ownership qualifiers其中一个来修饰:
- __strong(默认,如果不指定其他,编译器就默认加入)
- __weak
- __unsafe_unretained
- __autoreleasing
文章图片
image 比方说下面这段程序
NSError *error;
BOOL OK = [myObject performOperationWithError:&error];
if (!OK) {
// Report the error.
// ...
开启ARC经过编译器处理后将会变成下面这样:
NSError * __strong error;
NSError * __autoreleasing tmp = error;
BOOL OK = [myObject performOperationWithError:&tmp];
error = tmp;
if (!OK) {
// Report the error.
// ...
接下来看几个例子:
如果我想创建一个字符串,使用完之后将它释放调用,使用MRC管理内存的写法应该是这样:
- 5.1、__strong ownership qualifier
如果变量var被__strong修饰,当变量var指向某个对象objc,那么变量var持有某个对象objc的所有权
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];
//@"Hello, world"对象的RC=1
NSLog(@"%@", text);
[text release];
//@"Hello, world"对象的RC=0
}
而如果是使用ARC方式的话,text对象无需调用
release
方法,而是当text
变量超过作用域时,编译器来自动加入[text release]
方法来释放内存{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];
//@"Hello, world"对象的RC=1
NSLog(@"%@", text);
}
/*
*当text超过作用域时,@"Hello, world"对象会自动释放,RC=0
*/
而当你将text赋值给其他变量anotherText时,MRC需要retain一下来持有所有权,当text和anotherText使用完之后,各个调用release方法来释放。
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];
//@"Hello, world"对象的RC=1
NSLog(@"%@", text);
NSString *anotherText = text;
//@"Hello, world"对象的RC=1
[anotherText retain];
//@"Hello, world"对象的RC=2
NSLog(@"%@", anotherText);
[text release];
//@"Hello, world"对象的RC=1
[anotherText release];
//@"Hello, world"对象的RC=0
}
而使用ARC的话,则不需要调用retain和release方法来持有跟释放对象。
{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];
//@"Hello, world"对象的RC=1
NSLog(@"%@", text);
NSString *anotherText = text;
//@"Hello, world"对象的RC=2
NSLog(@"%@", anotherText);
}
/*
*当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法, @"Hello, world"对象的RC=0
*/
除了当
__strong
变量超过作用域时,编译器会自动加入release
语句来释放内存,如果你将__strong
变量重新赋给它其他值,那么编译器也会自动加入release
语句来释放变量指向之前的对象。例如:{
NSString *text = [[NSString alloc] initWithFormat:@"Hello, world"];
//@"Hello, world"对象的RC=1
NSString *anotherText = text;
//@"Hello, world"对象的RC=2
NSString *anotherText = [[NSString alloc] initWithFormat:@"Sam Lau"];
// 由于anotherText对象引用另一个对象@"Sam Lau",那么就会自动调用[anotherText release]方法,使得@"Hello, world"对象的RC=1, @"Sam Lau"对象的RC=1
}
/*
*当text和anotherText超过作用域时,会自动调用[text release]和[anotherText release]方法,
*@"Hello, world"对象的RC=0和@"Sam Lau"对象的RC=0
*/
前面已经提过内存管理的四条规则:
Objective-C对象的动作 | Objective-C对象的方法 |
---|---|
1. 创建一个对象并获取它的所有权 | alloc/new/copy/mutableCopy (RC = 1) |
2. 获取对象的所有权 | retain (RC + 1) |
3. 放弃对象的所有权 | release (RC - 1) |
4. 释放对象 | dealloc (RC = 0 ,此时会调用该方法) |
- 对于规则1和规则2,是通过
__strong
变量来实现, - 对于规则3来说,当变量超过它的作用域或被赋值或成员变量被丢弃时就能实现
- 对于规则4,当
RC=0
时,系统就会自动调用
- 5.2、__weak ownership qualifier
__strong
不能解决引用循环(Reference Cycle)
问题:对象A持有对象B,反过来,对象B持有对象A;这样会导致不能释放内存造成内存泄露问题。文章图片
image 举一个简单的例子,有一个类Test有个属性objc,有两个对象test1和test2的属性objc互相引用test1和test2:
@interface Test : NSObject
@property (strong, nonatomic) id objc;
@end{
Test *test1 = [Test new];
/* 对象a */
/* test1有一个强引用到对象a */Test *test2 = [Test new];
/* 对象b */
/* test2有一个强引用到对象b */test1.objc = test2;
/* 对象a的成员变量objc有一个强引用到对象b */
test2.objc = test1;
/* 对象b的成员变量objc有一个强引用到对象a */
}
/*当变量test1超过它作用域时,它指向a对象会自动release
*当变量test2超过它作用域时,它指向b对象会自动release
*
*此时,b对象的objc成员变量仍持有一个强引用到对象a
*此时,a对象的objc成员变量仍持有一个强引用到对象b
*于是发生内存泄露
*/
如何解决?于是我们引用一个
__weak ownership qualifier
,被它修饰的变量都不持有对象的所有权,而且当变量指向的对象的RC为0时,变量设置为nil。例如:__weak NSString *text = [[NSString alloc] initWithFormat:@"Sam Lau"];
NSLog(@"%@", text);
由于text变量被__weak修饰,text并不持有@"Sam Lau"对象的所有权,@"Sam Lau"对象一创建就马上被释放,并且编译器给出警告??,所以打印结果为(null)。
所以,针对刚才的引用循环问题,只需要将Test类的属性objc设置weak修饰符,那么就能解决。
@interface Test : NSObject
@property (weak, nonatomic) id objc;
//修改成weak修饰符
@end
以及我们常用的block防止内存泄漏也可以使用__weak 修饰符,引用官方给的例子如下:
MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
- 5.3、__unsafe_unretained ownership qualifier
__unsafe_unretained ownership qualifier
,正如名字所示,它是不安全的。它跟__weak
相似,被它修饰的变量都不持有对象的所有权,但当变量指向的对象的RC
为0时,变量并不设置为nil
,而是继续保存对象的地址;这样的话,对象有可能已经释放,但继续访问,就会造成非法访问(Invalid Access)。例子如下:
__unsafe_unretained id obj0 = nil;
{
id obj1 = [[NSObject alloc] init];
// 对象A
/* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */obj0 = obj1;
/* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */
NSLog(@"A: %@", obj0);
}
/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */NSLog(@"B: %@", obj0);
/* 由于obj0是__unsafe_unretained,当它指向的对象RC=0时,它会继续保存对象的地址,所以两个地址相同 */
打印结果是
内存地址相同
:文章图片
image 如果将__unsafe_unretained改为weak的话,两个打印结果将不同
__weak id obj0 = nil;
{
id obj1 = [[NSObject alloc] init];
// 对象A
/* 由于obj1是强引用,所以obj1持有对象A的所有权,对象A的RC=1 */obj0 = obj1;
/* 由于obj0是__unsafe_unretained,它不持有对象A的所有权,但能够引用它,对象A的RC=1 */NSLog(@"A: %@", obj0);
}
/* 当obj1超过它的作用域时,它指向的对象A将会自动释放 */NSLog(@"B: %@", obj0);
/* 由于obj0是__weak, 当它指向的对象RC=0时,它会自动设置为nil,所以两个打印结果将不同*/
文章图片
image
- 5.4、 __autoreleasing ownership qualifier
MRC
下autorelease pool
写法如下:NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// put object into pool
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
/* 超过autorelease pool作用域范围时,obj会自动调用release方法 */
引入
ARC
之后,写法更加简洁:@autoreleasepool {
id __autoreleasing obj = [[NSObject alloc] init];
}
相比之前的创建、使用和释放
NSAutoreleasePool
对象,现在你只需要将代码放在@autoreleasepool
块即可。你也不需要调用autorelease
方法了,只需要用__autoreleasing
修饰变量即可。文章图片
image 但是我们很少或基本上不使用
autorelease pool
。当我们使用XCode
创建工程后,有一个app
的入口文件main.m
使用了它:int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
六、 Property(属性)与ownership qualifier有了ARC之后,新的property modifier也被引入到Objective-C类的property,例如:
@property (strong, nonatomic) NSString *text;
下面有张表来展示property modifier与ownership qualifier的对应关系
Property modifier | Ownership qualifier |
---|---|
strong | __strong |
retain | __strong |
copy | __strong |
weak | __weak |
assign | __unsafe_unretained |
unsafe_unretained | __unsafe_unretained |
- 先看下命名一个属性为
retain
时,然后调用@synthesize
时编译器将会做什么
setter方法:
- (void)setCount:(NSNumber *)newCount {
[newCount retain];
[_count release];
// Make the new assignment.
_count = newCount;
}
也就是这里会首先retain新值,然后释放旧值(旧值RC -1),然后再赋值新值。
- 相同的当命名一个属性为copy类型时
setter方法:
- (void)setCount:(NSNumber *)newCount {
[_count release];
// Make the new assignment.
_count = [newCount copy];
}
retain与copy的区别在于,一个不产生新的对象只是对对象的RC + 1,另一个产生新的对象。
- 除了上面属性修饰符外还有
atomic (default)
、nonatomic
、readonly
、readwrite
。
文章图片
image
只能一个线程访问
,线程安全的(相当于线程锁的概念,一个时间段只有一个线程访问不容易出错,所以线程安全),低性能
文章图片
image
七、内存管理问题解决办法
文章图片
image 在往下看之前请下载实例MemoryProblems,我们将以这个工程展开如何检查和解决内存问题。
- 悬挂指针问题
当你运行MemoryProblems后,点击悬挂指针那个选项,就会出现
EXC_BAD_ACCESS
崩溃信息文章图片
image 我们看看这个
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
,并点击文章图片
image 【内存管理概念与原理以及解决办法】2、Run -> Diagnostics -> Enable Zombie Objects
文章图片
image 设置完之后,再次运行和点击悬挂指针,虽然会再次crash,但这次控制台打印了以下有用信息:
文章图片
image 信息
message sent to deallocated instance 0x7fe19b081760
大意是向一个已释放对象发送信息,也就是已释放对象还调用某个方法。现在我们大概知道什么原因导致程序会crash
,但是具体哪个对象被释放还仍然使用呢?点击上面红色框的
Continue program execution
按钮继续运行,截图如下:文章图片
image 留意上面的两个红色框,它们两个地址是一样,而且
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
*/
}
解决:
@property (strong, nonatomic) ArrayDataSource *dataSource;
//strong代替assign
八、内存泄露问题
使用Instruments里面的子工具Leaks来检查内存泄露。
- 静态分析
启动程序后,点击静态分析,马上就出现crash
文章图片
image 此时,即使启用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
-
文章图片
image - 自动静态分析:在Build Settings启用Analyze During 'Build',每次编译时都会自动静态分析
文章图片
image 静态分析结果如下:
文章图片
image 通过静态分析结果,我们来分析一下为什么NSZombieEnabled不能定位
EXC_BAD_ACCESS
的错误代码位置。由于callback传入进来的是null指针
,而NSZombieEnabled
只能针对某个已经释放对象的地址
,所以启动NSZombieEnabled是不能定位的,不过可以通过静态分析可得知。- 启动Instruments
静态分析
能够检查出一些内存泄露
问题,但是有时只有运行时
使用Instruments
才能检查到,启动Instruments
步骤如下:- 点击Xcode的菜单栏的 Product -> Profile 启动Instruments
文章图片
image - 此时,出现Instruments的工具集,选中Leaks子工具点击
文章图片
image - 打开Leaks工具之后,点击
红色圆点
按钮启动Leaks工具,在Leaks工具启动同时,模拟器或真机也跟着启动
文章图片
image
- 启动Leaks工具后,它会在程序
运行时
记录内存分配信息和检查是否发生内存泄露。当你点击引用循环进去那个页面后,再返回到主页,就会发生内存泄露
文章图片
image
文章图片
image
九、定位内存泄露
借助Leaks能很快定位内存泄露问题,在这个例子中,步骤如下:
- 首先点击Leak Checks时间条那个红色叉
文章图片
image - 然后双击某行内存泄露调用栈,会直接跳到内存泄露代码位置
文章图片
image
十、难以检测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");
}
- 我们首先用静态分析来看看能不能检查出内存泄露:
文章图片
image
- 结果是没有任何内存泄露的提示,我们再用Instrument Leak工具在运行时看看能不能检查出:
文章图片
image 结果跟使用静态分析一样,还是没有任何内存泄露信息的提示。
那么我们怎么知道这个
BlockLeakViewController
发生了内存泄露呢?还是根据iOS/OS X
内存管理机制的一个基本原理:当某个对象的引用计数为0
时,它就会自动调用- (void)dealloc
方法。在这个例子中,如果
BlockLeakViewController
被navigationController pop
出去后,没有调用dealloc
方法,因为BlockLeakViewController 强引用了dataSource,而dataSource的configureCellBlock属性又强引用了BlockLeakViewController的实例方法,导致循环引用,BlockLeakViewController不能被释放。我们在
dealloc
方法打印release BlockLeakViewController
信息:- (void)dealloc
{
NSLog(@"release BlockLeakViewController");
}
在点击返回按钮后,其并没有打印出来,因此这个BlockLeakViewController存在内存泄露问题的。解决:
BlockLeakViewController *__weak controller = self;
[controller configureCell];
//代替[self configureCell];
总结 在创建工程的时候,在
Build Settings
启用Analyze During Build
,每次编译时都会自动静态分析
。编码时及时知道是否存在内存泄露
或其他bug问题,并且进行修改。而在运行过程中,如果出现EXC_BAD_ACCESS
,则启用NSZombieEnabled
,看出现异常后,在控制台是否能打印出更多的提示信息。如果想在运行时
查看是否存在内存泄露
,使用Instrument Leak工具
。但是有些内存泄露
是很难检查出来,只有通过手动覆盖dealloc方法
,看它最终有没有调用来判断是否存在内存泄漏。参考借鉴文献:
IOS内存管理(一)基本概念与原理
IOS内存管理(二)借助工具解决内存问题
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 基于微信小程序带后端ssm接口小区物业管理平台设计
- 2020-04-07vue中Axios的封装和API接口的管理
- 事件处理程序
- 阿菘的ScalersTalk第五轮新概念朗读持续力训练Day15|阿菘的ScalersTalk第五轮新概念朗读持续力训练Day15 20191025
- 全过程工程咨询——时间管理(12)
- 《卓有成效的管理者》第二十二堂课(创造英雄)
- 游乐园系统,助力游乐园管理
- #山言良语#用管理思维百天减肥18斤
- 最有效的时间管理工具(赢效率手册和总结笔记)