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方法,对大图片进行缩放,减少图片解码占用内存大小。
方式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 加速下的图像处理
- App 的 CPU 占用率计算分析
+ (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;
}
- App 的 内存 占用率计算分析
- (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;
}
}