这篇主要从以下几个方面入手:
- 自动释放池
- 引起内存泄漏的原因
- 内存问题检测方法
- 优化建议(启动、界面、耗能等)(下篇)
- 应用瘦身(下篇)
1. 自动释放池 autoreleasepool
autorelease 本质就是 延迟调用release
终端执行命令clang -rewrite-objc main.m
65 warnings generated.
没有报错,生成 main.cpp
的c++文件,导入到Xcode工程中。
注意main.cpp
添加到工程后需要取消target不能参与编译
main.cpp底部的代码:
{
__AtAutoreleasePool __autoreleasepool;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} 构造函数
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} 析构函数
void * atautoreleasepoolobj;
};
__AtAutoreleasePool结构体内部调用构造函数和析构函数,函数内部调用了 objc_autoreleasePoolPush,objc_autoreleasePoolPop
有些对构造函数、析构函数可能不熟悉,下面举个简单的例子进行说明:
进入大括号'{'
的时候会调用构造函数
,打印Test()
出了大括号'}'
的时候会调用析构函数
,会打印~Test()
。
进入大括号'{'
的时候会调用 objc_autoreleasePoolPush()
出了大括号'}'
的时候会调用 objc_autoreleasePoolPop(atautoreleasepoolobj)
atautoreleasepoolobj
对象 相当于哨兵
,以便 出了大括号'}'
后 释放对象
网上下载objc的源码,搜索
objc_autoreleasePoolPush
,通过终端main.cpp和objc源码可以发现以下三点:
-
自动释放池的主要结构体和类是:__AtAutoreleasePool、AutoreleasePoolPage
-
调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的
-
AutoreleasePoolPage的大小都是4096个字节
自动释放池是 AutoreleasePoolPage 是以双向链表的形式连接起来的,每一页是4096,当前页满了,会添加到下一页中。
AutoreleasePoolPage类本身占用56个字节,容量是4096个字节。
下面分析AutoreleasePoolPage
由于arc
不能直接调用autorelease
方法,所以需要去掉arc,如下图:
查看自动释放池的情况
extern void _objc_autoreleasePoolPrint(void);
总结:
- 自动释放池是由AutoreleasePoolPage以
双向链表的方式
实现的 - 当对象调用autorelease方法时, 会将
延迟释放的对象加入AutoreleasePoolPage中
- 调用pop方法是, 会向
栈中的对象发送release消息
1.1 大量临时数据的处理
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) {
@autoreleasepool {//要写在循环内部,每次调用fileContents后及时释放堆上的内存,而不是等所有循环都执行完,内存很高以后才释放,这样就没太多意义了。
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:url
encoding:NSUTF8StringEncoding
error:&error];
}
}
1.2 自定义线程中的自动释放池块
-(void)myThreadStart:(id)obj {
@autoreleasepool {
//新线程的代码
}
}
//其他地方
{
NSThread *myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(myThreadStart:)
object:nil];
[myThread start];
}
栈的内存 ,子线程512kb,主线程,模拟器8m,真机1m,如果for循环产生了大量的临时变量,内存暴增,会把主线程或者子线程堵塞住,手机越来越卡,一直到死机。使用autoreleaspool可以将在每次执行完一次遍历后及时的释放掉 {}中的栈上的变量。这是系统的autoreleaspool无法清理的,需要pop后才能销毁当前页面主线程的数据。 线程堵塞
2. 引起内存泄漏的原因
1.循环引用 2.强引用 3.非OC对象,没有手动释放
内存泄漏会导致`内存不能正常回收`,当内存增长到大概120m(具体多少后续补充)后会导致程序闪退。
正常的内存回收过程
ClassA* a = [ClassA new];
a.b = [ClassB new];
非正常的内存回收过程
ClassA* a = [ClassA new];
ClassB* b = [ClassB new];
a.b = b;
b.a = a;
block循环引用:
使用__StrongSelf typeof(self) = weakSelf 延长weakSelf对象的生命周期 vc调用pop的时候,对象已经释放,延迟回调时weakSelf为空,weakself.property导致程序不可控,甚至闪退。
NSTimer循环引用
不管是 target-action 还是block的方式执行nstimer都会造成循环引用,block的方式一开始会释放,但是马上又会持有。
timer是运行在runloop上的
RunLoop -> timer --> self
解决方法:
2.1 利用调用过程,代理函数将nstimer失效.
2.2 中间类的方式 RunLoop -> 自定义timer -> target(weakself) --> self
2.3 runtime动态添加方法:RunLoop -> timer -> target <-- self
self.target = [NSObject new];
class_addMethod([self.target class], @selector(fire), (IMP)fire, "v@:");
void fire() {
NSLog(@"%s", __func__);
}
pop以后self释放,self.target没有持有self,self.target也释放
2.4 利用NSProxy
3. 内存问题检测方法
代码下载地址: github.com/tanghaitao/…
3.1 静态检测方法(野指针、手动Analyze、自动)
3.1.1 野指针(只能检测OC对象)
**野指针只能检测OC对象**
,c语言的检测不到
操作方法: Edit Scheme
-> Run
-> Diagnostics
-> 勾上 Zombie Objects
, 效果如下图:
没有勾选
Zombie Objects
的效果图:
勾选
Zombie Objects
的效果图:
@property (assign, nonatomic) GVDataSource *dataSource;//assign的oc对象释放了,指针还会存在.
3.4 静态分析(循环引用的不能检测出来)
循环引用的不能检测出来
3.4.1 手动Analyze
菜单栏,Product
-> Analyze
, **先运行,error,再选择检测方法**
CGPathRef手动释放,File的close等非oc对象,
3.4.2 自动Analyze
Build Settings
里面搜索Analyze
,设置为Yes
,每次编译或运行都会自动的分析一遍代码
3.2 动态监测方法(instruments 第三方工具检测)
3.2.1 instruments
instruments系统的代码进入不了,检测不到, 而且它是每隔一段时间采样,并不是百分百就能检测不到的。
过滤调用链显示
3.2.2 第三方工具检测(限view和vc)
MLeaksFinder 局限性: 只能检测视图和vc
核心: 在界面完全消失,并控制器的状态是出栈状态,这时,观察延时观察对象是否存活