2018-06-29 学习iOS性能分析和优化

本文章参考自iOS性能分析和优化,主要学习如何使用性能分析工具Instruments。

一.基本概念

1.内存空间的划分

一个进程占用的内存空间,包含5种不同的数据区:
(1) BSS段:通常存放未初始化的全局变量
(2)数据段:通常存放已初始化的全局变量
(3)代码段:通常存放程序执行代码
(4)堆:通常存放进程运行中被动态分配的内存段,OC对象(所有继承自NSObject的对象)就存放在堆里。
(5)栈:由编译器自动分配释放,存放函数的参数值,局部变量等值。

其中,栈内存是由系统来管理的,因此我们常说的内存管理,是指堆内存的管理,也就是所有OC对象的创建和销毁的管理。随着苹果推出了ARC,编译器会自动生成内存管理的代码,因此我们的内存泄漏通常来源于堆内存。

2.内存泄漏(Memory Leak)

用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,即该内存空间使用完毕之后未回收。在ios的内存泄漏原因一般有循环引用、错用Strong/copy等。

二.Instruments多功能检测

1.静态分析(Analyze)

不需要运行程序,就可以检查到存在内存泄漏的地方。


2018-06-29 学习iOS性能分析和优化_第1张图片

常见的三种泄露情形:

  1. 创建了一个对象但是并没有使用,提示:Value Stored to 'number' is never read
  2. 创建了一个(指针可变的)对象,且初始化了,但是初始化的值一直没读取过,提示:Value Stored to 'str' during its initialization is never read
  3. 调用了让某个对象引用计数加一的函数,但没有调用相应的让其引用计数减一的函数,提示:Potential leak of an object stored into 'subImageRef'

通过测试可以发现,程序中有这样的泄露:



原来是因为这个指针初始化之后没有使用,其实在这里如果要赋值的话,直接变成这样:

NSDictionary *cover = self.covers[indexPath.item];

2.内存泄漏检查工具——Leak

参考Xcode结合Leaks检测内存泄露

2018-06-29 学习iOS性能分析和优化_第2张图片

2018-06-29 学习iOS性能分析和优化_第3张图片

中途有点报错, 关闭SIP就好了。
2018-06-29 学习iOS性能分析和优化_第4张图片

2018-06-29 学习iOS性能分析和优化_第5张图片

哇,吓我一跳,这么多内存泄漏的吗..赶紧参考了 AFNetworking 3.0中调用[AFHTTPSessionManager manager]方法导致内存泄漏的解决办法,发现问题在于,由于ARC的机制,导致每当实例化 Session类后都没有地方释放掉实例,需要把 Session类的实例都改成单例模式。


(附)科普:单例模式

参考iOS-单例模式简单使用

1.概念

一个类只允许有一个实例,在整个程序中需要多次使用,共享同一份资源的时候,就可以创建单例,一般封装成工具类使用,如UIApplication,NSUserDefaults,NSNotificationCenter,NSFileManager等。

2.单例优缺点

优点:单例模式会使类只有一个实例,所以方便使用,并且节省内存资源的分配。因为使用GCD的方式是线程安全的,所以会避免资源的多重使用。

缺点:单例创建的内存只有在程序结束的时候才会被释放。由于单例不能被继承(因为返回的是同一个实例),所以扩展性很不好。


由此,我们首先建立一个AFSessionSingleton文件,里面包含的是单例模式的声明。

#import "AFSessionSingleton.h"

@implementation AFSessionSingleton

static AFHTTPSessionManager *manager;

+ (AFHTTPSessionManager *) sharedHttpSessionManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [AFHTTPSessionManager manager];
        manager.requestSerializer.timeoutInterval = 10.0;
    });
    return manager;
}

@end

在调用的时候变成这一句:

//    AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
AFHTTPSessionManager *session = [AFSessionSingleton sharedHttpSessionManager];

就可以用单例模式避免内存泄漏了!

其他问题:还有一个Leak,但是通过debug发现没什么事,就不管了。

2018-06-29 学习iOS性能分析和优化_第6张图片

2018-06-29 学习iOS性能分析和优化_第7张图片

3.自己制造一个Leak出来

参考iOS开发中本人或同事碰到的内存泄漏及解决办法

循环引用
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSMutableArray *arr1 = [NSMutableArray array];
    NSMutableArray *arr2 = [NSMutableArray array];
    
    [arr1 addObject: arr2];
    [arr2 addObject: arr1];
}
2018-06-29 学习iOS性能分析和优化_第8张图片

他说的那种方法还是会导致内存泄漏啊..以后还是尽量避免循环引用吧。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSMutableArray *arr1 = [NSMutableArray array];
    NSMutableArray *arr2 = [NSMutableArray array];
    
    __weak typeof (arr1) weakArr1 = arr1;
    
    [weakArr1 addObject: arr2];
    [arr2 addObject: weakArr1];
}
2018-06-29 学习iOS性能分析和优化_第9张图片

4.Allocations——内存分配

用于检测程序运行过程中的内存分配情况。

2018-06-29 学习iOS性能分析和优化_第10张图片

5.Time Profiler

分析代码的执行时间,以便找到导致程序变慢的原因。

当点击Time Profiler应用程序开始运行后.就能获取到整个应用程序运行消耗时间分布和百分比.为了保证数据分析在统一使用场景真实行有如下点需要注意:

在开始进行应用程序性能分析前,请一定要使用真机,因为模拟器运行在Mac上,然而Mac上的CPU往往比iOS设备要快。相反,Mac上的GPU和iOS设备的完全不一样,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操作在模拟器上运行的更慢,尤其是使用CAEAGLLayer来写一些OpenGL的代码时候。这就导致模拟器性能数据和用户真机使用性能数据相去甚远。

可以看到,加载视频是最花时间的,然后就是加载瀑布流图片。

2018-06-29 学习iOS性能分析和优化_第11张图片

三.一些总结

1.以下情况会增加APP的内存占用

  1. 创建对象,定义变量。
  2. 调用一个函数或方法。

2.以下情况会增加CPU的消耗

  1. 创建对象、调整对象属性、销毁对象。
  2. 布局计算和AutoLayout。
  3. 文本的计算和渲染。
  4. 图片的解码和绘制。

3.如何避免内存泄漏

  1. 做好cell等可复用对象的重用
  2. 可以只创建一次的对象,不要创建多次(如页面的某个功能弹窗)
  3. 用较少的对象和方法调用去实现功能
  4. 将耗时的操作放在子线程

4.一些关于ViewController的内存泄漏

参考iOS学习——内存泄漏检查及原因分析

在目前主要以ARC进行内存管理的开发模式,导致内存泄漏的根本原因是代码中存在循环引用,从而导致一些内存无法释放,这就会导致dealloc()方法无法被调用。主要原因大概有一下几种类型。

1) ViewController中存在NSTimer
NSTimer scheduledTimerWithTimeInterval:1.0 
                                 target:self 
                               selector:@selector(updateTime:) 
                               userInfo:nil 
                                repeats:YES];

如果你的ViewController中有NSTimer,那么你就要注意了,因为当你调用target:self时,就会增加ViewControllerreturn count,如果不将这个Timer invalidate,将不会调用dealloc

2) ViewController中的代理delegate

一个比较隐秘的因素,要找一下这个类有关的代理,看看有没有强引用属性,如果这个View Controller需要外部传入某个Delegate进来,来通过Delegate+protocol的方式传参数给其他对象,那么这个delegate一定不要强引用,尽量assign或者weak,否则整个View Controller会持续持有这个delegate,直到自身被释放。

3) ViewController中的Block

这个问题会比较容易犯,Block体内使用实例变量也会造成循环引用,使得拥有这个实例的对象不能释放。因为该Block本来就是当前View Controller的一部分,现在Block又强引用self,导致循环引用无法释放。

例如:有一个类叫OneViewController,有个属性是NSString *name,如果在block体内使用了self.name或者_name,这个类就无法释放了。

解决方法就是在Block之前声明当前的self为弱引用。

__weak ViewController *weasSelf = self;
4) ViewController的子视图对self的持有

我们有时候需要在自视图或者某个cell中点击跳转等操作,需要在自视图或者cell中持有当前的ViewController对象,这样跳转之后的back键才能直接返回该页面,同时不销毁当前的ViewController。此时,就需要注意在子视图或者cell中对当前页面的持有对象不能是强引用,尽量用assign或者weak,否则会造成循环引用,导致内存无法释放。

你可能感兴趣的:(2018-06-29 学习iOS性能分析和优化)