性能优化

245 阅读5分钟

CPU和GPU简介

  • CPU - Central Processing Unit,中央处理器,在iOS程序中主要负责以下事情:

    1.负责对象的创建和销毁、对象属性的调整

    2.布局的计算、文本的计算和排版规格

    3.图片的格式转码和解码、图像的绘制(Core Graphic

  • GPU - Graphics Processing Unit,图形处理器,负责纹理的渲染。

    如果没有接触过OpenGL的同学,可能不太好理解纹理渲染这个概念,我们知道,屏幕上面的物理元件是像素,我们在屏幕上面看到的图片,文字,视频,就是由屏幕上的所有像素,通过控制色值变化而呈现出来的。那么像素的色值数据,就是由GPU计算得出的,然后将这些数据提交给对应的控制器,由它负责显示到屏幕上。

为什么要减少内存

简单来说:更快的启动速度,不会因为内存过大而导致 Crash,可以让 App 存活更久

iPhone分辨率

各个设备的分辨率如下: 查看

使用了不合理的API

1显示频率很低的大图片资源使用[UIImage imageNamed:]方法进行加载的话,系统实际上只是在 Bundle 内查找到文件名,然后把这个文件名放到 UIImage 里返回,并没有进行实际的文件读取和解码,当UIImage 第一次显示到屏幕上时,其内部的解码方法才会被调用,同时解码结果会保存到一个全局缓存。

此API的调用会导致图片加载后系统会进行缓存,但是苹果又没有提供对应的API进行清理。对于此种情况我们开发的时候很常见,对此我们的处理方式如下:

采用这个API[UIImage imageWithContentsOfFile:][[UIImage alloc] initWithContentsOfFile:],此系统不会进行缓存处理,当图片没有再被引用时,其占用的内存会被彻底释放掉。

2.使用-[UIColor colorWithPatternImage:]这个API去修改UIImage对象的颜色,此API会导致在内存中多生成一个图像,内存占用很大,对于此种情况有如下API供我们使用,如下:图片以UIImageRenderingModeAlwaysTemplate的方式进行加载

view.tintColor = theColor;
UIImage *image = [[UIImage imageNamed:name] imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate]

3.根据颜色生成图片时,尺寸较大

举个简单例子:一个大小590KB的图片,分辨率是 2048px * 1536px,它实际使用的内存不是590KB,而是2048 * 1536 * 4 = 12 MB。 对于下面的函数后面会详细说明

//外部应该调用此方法,创建出1px宽高的小图像
+ (UIImage*)createImageWithColor:(UIColor *)color {
    return [self createImageWithColor: color andSize: CGSizeMake(1, 1)];
}

+ (UIImage*)createImageWithColor:(UIColor*)color andSize:(CGSize)size
{
    CGRect rect=CGRectMake(0,0, size.width, size.height);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}

4.使用ImageIO方法,对大图片进行缩放,减少图片解码占用内存大小。

ImageIO 相关API框架详解

方式1:

- (UIImage *)resizeScaleImage:(CGFloat)scale {
    
    CGSize imgSize = self.size;
    CGSize targetSize = CGSizeMake(imgSize.width * scale, imgSize.height * scale);
    NSData *imageData = UIImageJPEGRepresentation(self, 1.0);
    CFDataRef data = (__bridge CFDataRef)imageData;
    
    CFStringRef optionKeys[1];
    CFTypeRef optionValues[4];
    optionKeys[0] = kCGImageSourceShouldCache;
    optionValues[0] = (CFTypeRef)kCFBooleanFalse;
    CFDictionaryRef sourceOption = CFDictionaryCreate(kCFAllocatorDefault, (const void **)optionKeys, (const void **)optionValues, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CGImageSourceRef imageSource = CGImageSourceCreateWithData(data, sourceOption);
    CFRelease(sourceOption);
    if (!imageSource) {
        NSLog(@"imageSource is Null!");
        return nil;
    }
    //获取原图片属性
    int imageSize = (int)MAX(targetSize.height, targetSize.width);
    CFStringRef keys[5];
    CFTypeRef values[5];
    //创建缩略图等比缩放大小,会根据长宽值比较大的作为imageSize进行缩放
    keys[0] = kCGImageSourceThumbnailMaxPixelSize;
    CFNumberRef thumbnailSize = CFNumberCreate(NULL, kCFNumberIntType, &imageSize);
    values[0] = (CFTypeRef)thumbnailSize;
    keys[1] = kCGImageSourceCreateThumbnailFromImageAlways;
    values[1] = (CFTypeRef)kCFBooleanTrue;
    keys[2] = kCGImageSourceCreateThumbnailWithTransform;
    values[2] = (CFTypeRef)kCFBooleanTrue;
    keys[3] = kCGImageSourceCreateThumbnailFromImageIfAbsent;
    values[3] = (CFTypeRef)kCFBooleanTrue;
    keys[4] = kCGImageSourceShouldCacheImmediately;
    values[4] = (CFTypeRef)kCFBooleanTrue;
    
    CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void **)keys, (const void **)values, 4, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CGImageRef thumbnailImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options);
    UIImage *resultImg = [UIImage imageWithCGImage:thumbnailImage];
    
    CFRelease(thumbnailSize);
    CFRelease(options);
    CFRelease(imageSource);
    CFRelease(thumbnailImage);
    
    return resultImg;
}

方式2:(不推荐)

+ (UIImage*)OriginImage:(UIImage *)image scaleToSize:(CGSize)size 
{
// 创建一个bitmap的context 
// 并把它设置成为当前正在使用的context 
UIGraphicsBeginImageContext(size); // 绘制改变大小的图片 [image drawInRect:CGRectMake(0, 0, size.width, size.height)]; 
// 从当前context中创建一个改变大小后的图片
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext(); 
// 使当前的context出堆栈
UIGraphicsEndImageContext(); 
// 返回新的改变大小后的图片 
return scaledImage; 
}

采用方式1使用ImageIO框架,直接读取图像大小和元数据信息,不会带来额外的内存开销。网上可查询下别人的测试案例,大概缩减40%-50%的样子。

5.自定义绘制使用drawRect

这里有一个准则,万不得已,不要重载drawRect函数。原因如下:

重载drawRect函数会导致系统给UIView创建一个backing store, 像素的数量是UIView大小乘以 contentsScale 的值。因此会耗费不少内存。

UIView 并不是一定需要一个backing store的,比如设置背景颜色就不需要(除非是 pattern colors)。如果需要设置复杂的背景颜色,可以直接通过 UIImageView 来实现。

WWDC2018图像和图形最佳实践 针对这个有篇心得 点击学习

继续延伸 GPU 加速下的图像处理

+ (CGFloat)cpuUsage {
    kern_return_t kr;
    mach_msg_type_number_t count;
    static host_cpu_load_info_data_t previous_info = {0, 0, 0, 0};
    host_cpu_load_info_data_t info;
    
    count = HOST_CPU_LOAD_INFO_COUNT;
    
    kr = host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&info, &count);
    if (kr != KERN_SUCCESS) {
        return -1;
    }
    
    natural_t user   = info.cpu_ticks[CPU_STATE_USER] - previous_info.cpu_ticks[CPU_STATE_USER];
    natural_t nice   = info.cpu_ticks[CPU_STATE_NICE] - previous_info.cpu_ticks[CPU_STATE_NICE];
    natural_t system = info.cpu_ticks[CPU_STATE_SYSTEM] - previous_info.cpu_ticks[CPU_STATE_SYSTEM];
    natural_t idle   = info.cpu_ticks[CPU_STATE_IDLE] - previous_info.cpu_ticks[CPU_STATE_IDLE];
    natural_t total  = user + nice + system + idle;
    previous_info    = info;
    
    return (user + nice + system) * 100.0 / total;
}
- (NSUInteger)getApplicationUsedMemory
{
    struct mach_task_basic_info info;
    mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT;
	
	int r = task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)& info, & count);
	if (r == KERN_SUCCESS)
	{
		return info.phys_footprint;
	}
	else
	{
		return -1;
	}
}

头条抖音如何实现 OOM 崩溃率下降 50%+

Crash 分析