iOS性能优化(中)

656 阅读4分钟

这篇主要从以下几个方面入手:

  1. 自动释放池
  2. 引起内存泄漏的原因
  3. 内存问题检测方法
  4. 优化建议(启动、界面、耗能等)(下篇)
  5. 应用瘦身(下篇)

1. 自动释放池 autoreleasepool

autorelease 本质就是 延迟调用release 终端执行命令clang -rewrite-objc main.m 65 warnings generated. 没有报错,生成 main.cpp的c++文件,导入到Xcode工程中。

image.png

image.png

注意main.cpp添加到工程后需要取消target不能参与编译

main.cpp底部的代码: image.png

{
 
 __AtAutoreleasePool __autoreleasepool;
 
 }
 
 struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();} 构造函数
 ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);} 析构函数
  void * atautoreleasepoolobj;
 };

__AtAutoreleasePool结构体内部调用构造函数和析构函数,函数内部调用了 objc_autoreleasePoolPush,objc_autoreleasePoolPop

有些对构造函数、析构函数可能不熟悉,下面举个简单的例子进行说明:

image.png 进入大括号'{'的时候会调用构造函数,打印Test()

出了大括号'}'的时候会调用析构函数,会打印~Test()

进入大括号'{'的时候会调用 objc_autoreleasePoolPush()

出了大括号'}'的时候会调用 objc_autoreleasePoolPop(atautoreleasepoolobj)

atautoreleasepoolobj 对象 相当于哨兵,以便 出了大括号'}'后 释放对象

image.png 网上下载objc的源码,搜索 objc_autoreleasePoolPush,通过终端main.cpp和objc源码可以发现以下三点:

  1. 自动释放池的主要结构体和类是:__AtAutoreleasePool、AutoreleasePoolPage

  2. 调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的

  3. AutoreleasePoolPage的大小都是4096个字节

自动释放池是 AutoreleasePoolPage 是以双向链表的形式连接起来的,每一页是4096,当前页满了,会添加到下一页中。

image.png AutoreleasePoolPage类本身占用56个字节,容量是4096个字节。 image.png

下面分析AutoreleasePoolPage 由于arc不能直接调用autorelease方法,所以需要去掉arc,如下图: image.png

查看自动释放池的情况 extern void _objc_autoreleasePoolPrint(void);

image.png

image.png

image.png

总结:

  1. 自动释放池是由AutoreleasePoolPage以双向链表的方式实现的
  2. 当对象调用autorelease方法时, 会将延迟释放的对象加入AutoreleasePoolPage中
  3. 调用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];    

image.png 非正常的内存回收过程

     ClassA* a = [ClassA new];
     ClassB* b = [ClassB new];
     a.b = b;
     b.a = a;

image.png

block循环引用:

image.png 使用__StrongSelf typeof(self) = weakSelf 延长weakSelf对象的生命周期 vc调用pop的时候,对象已经释放,延迟回调时weakSelf为空,weakself.property导致程序不可控,甚至闪退。

NSTimer循环引用

image.png

image.png 不管是 target-action 还是block的方式执行nstimer都会造成循环引用,block的方式一开始会释放,但是马上又会持有。 timer是运行在runloop上的 RunLoop -> timer --> self 解决方法:

2.1 利用调用过程,代理函数将nstimer失效.

image.png

2.2 中间类的方式 RunLoop -> 自定义timer -> target(weakself) --> self

image.png

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__);
}

image.png pop以后self释放,self.target没有持有self,self.target也释放

2.4 利用NSProxy

image.png

3. 内存问题检测方法

代码下载地址: github.com/tanghaitao/…

3.1 静态检测方法(野指针、手动Analyze、自动)

3.1.1 野指针(只能检测OC对象)

**野指针只能检测OC对象**,c语言的检测不到 操作方法: Edit Scheme -> Run -> Diagnostics -> 勾上 Zombie Objects, 效果如下图: image.png 没有勾选 Zombie Objects的效果图: image.png

勾选 Zombie Objects的效果图: image.png @property (assign, nonatomic) GVDataSource *dataSource;//assign的oc对象释放了,指针还会存在.

3.4 静态分析(循环引用的不能检测出来)

循环引用的不能检测出来

3.4.1 手动Analyze

菜单栏,Product-> Analyze, **先运行,error,再选择检测方法** CGPathRef手动释放,File的close等非oc对象, image.png

image.png

3.4.2 自动Analyze

Build Settings里面搜索Analyze,设置为Yes,每次编译或运行都会自动的分析一遍代码 image.png

image.png

3.2 动态监测方法(instruments 第三方工具检测)

3.2.1 instruments

instruments系统的代码进入不了,检测不到, 而且它是每隔一段时间采样,并不是百分百就能检测不到的。

image.png

image.png

image.png 过滤调用链显示

3.2.2 第三方工具检测(限view和vc)

MLeaksFinder 局限性: 只能检测视图和vc

image.png 核心: 在界面完全消失,并控制器的状态是出栈状态,这时,观察延时观察对象是否存活

3.3 dealloc