iOS-性能优化的个人思路

技 术 文 章 / 超 人


本文所有内容都是经过亲测实践过的,绝无虚假。内容仅代表个人编写文章时的技术水平和思维广度,所以本文会随着个人提升而不断更新。如有错误或者大神指教,请留言,万分感谢。

文本大纲

  • 逻辑优化

    • 代码封装优化
    • 代码执行效率优化
  • 界面优化

    • 离屏渲染优化
    • 界面加载优化

逻辑优化

代码封装优化

代码的封装优化主要是细化代码的功能,每个功能单独提取出来做成一个方法,当其他地方需要用到同样功能时直接调用该方法即可,无需写重复代码,减少代码量,增加代码的重用性,方便单元测试。
例如:一个过滤输入文本内容的方法,需要过滤特殊字符和表情

- (void)filterCharactorString:(NSString *)string
{
/*过滤表情*/
NSString *modifiedString;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^\\u0020-\\u007E\\u00A0-\\u00BE\\u2E80-\\uA4CF\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFFEF\\u0080-\\u009F\\u2000-\\u201f\r\n]"options:NSRegularExpressionCaseInsensitive error:nil];
    modifiedString = [regex stringByReplacingMatchesInString:string
                                                               options:0
                                                                 range:NSMakeRange(0, [text length])
                                                          withTemplate:@""];
/*过滤特殊字符*/
NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"@/:;()¥「」"、[]{}#%-*+=_\\|~<>$€^•'@#$%^&*()_+'\""];
int i = 0;
    while (i < modifiedString.length) {
        NSString *rangeString = [modifiedString substringWithRange:NSMakeRange(i, 1)];
        NSRange range = [rangeString rangeOfCharacterFromSet:set];
        if (range.length == 0) {
            modifiedString = [modifiedString stringByReplacingCharactersInRange:NSMakeRange(i, 1) withString:@""];
        }
        i++;
    }
  return modifiedString;
}

上面的方法虽然实现了需要的功能,但是却显不灵活。假如我只想过滤表情,只想过滤特殊字符或者想过滤其他的内容,则需要重新写一个方法来满足功能。但是功能内部的代码却大致相同。这样就增加了代码量且使得代码看起来非常臃肿。
对上面的代码进行封装优化的方案有很多种,见仁见智,主要在思路而不在方法。
例如:我选择把过滤表情单独的提取出来,成一个根据正则表达式来过滤内容的方法,而过滤特殊字符串提取出来,成一个根据传入的字符来过滤内容的方法。

/**
 根据正则表达式过滤文字
 
 @param string 需要校验的文字
 @param regexStr 用以校验的正则表达式
 @return 过滤后的文字
 */
+ (NSString *)filterCharactor:(NSString *)string withRegex:(NSString *)regexStr
{
    NSString *searchText = string;
    NSError *error = NULL;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:&error];
    NSString *result = [regex stringByReplacingMatchesInString:searchText options:NSMatchingReportCompletion range:NSMakeRange(0, searchText.length) withTemplate:@""];
    return result;
}

/**
 根据传入的字符过滤文本内容
 
 @param string 需要过滤的原文本
 @param regexStr 需要过滤的字符内容
 @return 过滤后的文字
 */
+ (NSString *)filterSymbol:(NSString *)string withRegex:(NSString *)regexStr
{
    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:regexStr];
    int i = 0;
    while (i < string.length) {
        NSString *rangeString = [string substringWithRange:NSMakeRange(i, 1)];
        NSRange range = [rangeString rangeOfCharacterFromSet:set];
        if (range.length == 0) {
            string = [string stringByReplacingCharactersInRange:NSMakeRange(i, 1) withString:@""];
        }
        i++;
    }
  return string;
}

这样是方法中的功能性单一,但针对性却不单一。大大的提高了代码的重用性和单元测试。
代码的封装很重要,体现程序员的编程思维的远见性,代码的可扩展性。在合作开发时,能方便他人

代码执行效率优化
执行效率的优化主要在于得到结果的快慢,例如你想要一样东西,某宝和某东都有且价格差不多,但某宝要两天才能拿到,而某东当天下午就可以拿到。当然大家都会某东啦...这就是效率的优势。
关于代码的执行效率其实还有很多地方,碍于本人目前的眼界和水平有限,后期会验证后添加更多

  • 效率1:我们最常用的for循环
    NSMutableDictionary *dic = [NSMutableDictionary new];
    
    for (int i = 0 ; i < 100; i++) {
        [dic setObject:[NSString stringWithFormat:@"%i",i] forKey:[NSString stringWithFormat:@"%i",i]];
    }
    
    
    CFAbsoluteTime forStarTime = CFAbsoluteTimeGetCurrent();
    NSArray *dicValueArray = dic.allValues;
    for (int i = 0; i < dicValueArray.count; i++) {
        NSString *value = dicValueArray[i];
        NSLog(@"for----value:%@",value);
    }
    CFAbsoluteTime forEndTime = CFAbsoluteTimeGetCurrent() - forStarTime;
    
    CFAbsoluteTime forInStarTime = CFAbsoluteTimeGetCurrent();
    for (NSString *value in dic.allValues) {
        NSLog(@"forIn----value:%@",value);
    }
    CFAbsoluteTime forInEndTime = CFAbsoluteTimeGetCurrent() - forInStarTime;
    
    CFAbsoluteTime enumerateInStarTime = CFAbsoluteTimeGetCurrent();
    [dic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        NSLog(@"en----value:%@",obj);
    }];
    CFAbsoluteTime enumerateEndTime = CFAbsoluteTimeGetCurrent() - enumerateInStarTime;
    
    NSLog(@"for循环用时:%f",forEndTime);
    NSLog(@"forIn循环用时:%f",forInEndTime);
    NSLog(@"enumerateKeysAndObjectsUsingBlock用时:%f",enumerateEndTime);

执行的结果:

for循环用时:0.018385
forIn循环用时:0.017044
enumerateKeysAndObjectsUsingBlock用时:0.016417

看上去是enumerateKeysAndObjectsUsingBlock更快,但是执行多次后 ,你会发现,有时for快有时forin快有时enumerateKeysAndObjectsUsingBlock快。那是因为数据量比较少。
假如把上面代码里有100000个数据。
结果:

for循环用时:20.812115
forIn循环用时:21.940614
enumerateKeysAndObjectsUsingBlock用时:23.253821

for循环明显更快,不论尝试多少次结果都是for循环明显快。之所以用时20多秒是因为循环内部打印了日志,因为打印日志是非常耗时的操作。当然可以不用在内部去打印日志。结果依然是for循环更快。而往往在开发中,for循环内部执行的操作都是比较多并且耗时的。
所以在数据量小时for,forIn,enumerateKeysAndObjectsUsingBlock都可以。在大量数据时,尽量用for循环去执行。经过测试,执行效率上NSDictionary < NSArray < NSSet 。NSSet的执行效率最高

由此可见很多时候在获取特定的数据时算法的选择会即决定了代码执行次数也决定了执行效率。作为一个开发者要了解最基本的各类算法

冒泡排序、快速排序、插入排序、归并排序、希尔排序、动态排序。这些都是提供执行效率的基本算法。必须掌握了解的,这里就不详说了。不懂的朋友度娘


离屏渲染
说到离屏渲染,需要先了解屏幕每一帧界面是如何得到的。

  • 需要了解的知识点:
    屏幕显示原理

    首先从过去的CRT显示器原来说起,CRT的电子枪是按照上图的方式,从上到下一行一行扫描并曾现每帧画面的。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或其他硬件)会用硬件时钟产生一系列的信号,当电子枪换到新的一行,准备进行扫描时,显示器会发送一个水平同步信号(horizonal synchronization 简称HSync),而当一帧画面绘制完成后,电子枪回复到原位准备下一帧时,会发送一个垂直同步信号(vertical synchronization 简称VSync)。显示器通常以固定频率刷新,而这个频率就是VSync信号产生的频率。尽管现在的设备都是用液晶显示屏,但是原理仍然没有改变。

目前IOS设备采用的是双缓存+垂直同步,而Android在4.1后采用的是三缓存+垂直同步。
双缓存机制:GPU会预先渲染好一帧放入下一个缓存区内,让视频控制器取出,当下一帧渲染好后,GPU会直接把视频控制器的指针指向第二个缓冲器,如此来提高效率。即屏幕显示一帧,GPU预备下一帧。而不是显示完一帧在计算下一帧。


每一帧的由来,当收到VSync信号后,首先,系统图形服务会通过CADisplayLink等机制通知App,然后,App主线程开始在CPU中计算显示内容,比如视图等创建、布局、图片解码、文本绘制等,接着,CPU计算好的内容提交到GPU区,由GPU进行变换、合成、渲染。随后GPU将渲染结果提交到帧缓冲区,等到下一次收到VSync信号时显示上一帧计算好的内容。
界面卡顿,则是所谓的帧丢失,当在一个VSync信号内(每秒60帧,每一帧1/60秒),CPU或者GPU没有计算好内容,系统就会丢弃这一帧的内容,而屏幕依然显示的是之前的内容,就造成了界面卡顿。
GPU屏幕渲染有以下两种方式:

  • on-screen Rendering:在当前屏幕渲染,指的是GPU的渲染操作实在当前用于显示的屏幕缓冲区进行的;
  • off-screen Rendering:在当前屏幕以外的区域渲染,既离屏渲染,指的是在当前显示的屏幕缓冲区以外的区域开辟出来的一个新的缓冲区去进行渲染操作。

由上面知识点,可以看出离屏渲染是在GPU中造成的。

  • 离屏渲染: 所谓的离屏渲染即是在GPU计算时,由于界面层次复杂混合度大等造成计算的复杂度过大,导致GPU需要重新创建一个额外的屏幕外缓冲区计算这个位图。当计算好后在转换到帧缓冲区。这一次的渲染是脱离了屏幕而在屏幕以外的区域渲染完成的,所以叫做离屏渲染。

创建额外的屏幕外缓冲区去计算位图,再去替换屏幕内容的代价是非常大且耗时的。解决离屏渲染是提升用户体验非常重要的点,因为离屏渲染会导致帧丢失界面卡顿资源消耗。

补充知识点:
UIView和CALayer的关系
The Relationship Between Layers and Views 这里有一篇关于它俩关系的详细说明
简单的说UIView是基于CALayer进行封装的。UIView的每个属性都对应这CALayer的一个属性。

出自 WWDC 2012: iOS App Performance: Graphics and Animations

而CALayer负责显示UIView的具体内容,UIView负责提供内容和处理响应事件等,也就是说我们在手机上看见的都是CALayer所呈现的内容。下面是CALayer的结构图

CALayer结构图

CALayer由三个视觉元素组成,background背景 、contents内容、border边框。而中间的contents的属性声明为var contents: AnyObject?实际上它必须是个CGImage才能显示。

造成离屏渲染的点:

  • shouldRasterize(光栅化)

当设置shouldRasterize = YES时,会把光栅化的图片保存成一个bitmap缓存起来,当下一次要显示这个图层时,CPU会直接从缓存中拿取位图,传给GPU,而不需要GPU再去渲染这一部分的图层,减少GPU的渲染计算。 可以通过Instruments core animation或者模拟器 中的 Color Hits Green and Misses Red来查看图层是否被缓存了,绿色表示缓存,红色表示没有缓存。一般视图shouldRasterize默认为NO,对于经常变换的视图不要使用shouldRasterize。会造成性能消耗的浪费。
关于shouldRasterize是一个有取有舍的属性,对于那些复杂但是内容不长变的视图可以用shouldRasterize来缓存内容,减少GPU每次的计算,达到性能提高。但是要慎用,目前本人项目中还没有使用过shouldRasterize来缓存内容。

  • mask(遮罩层)

屏幕上的每一个像素点是由当前像素点上多层layer通过GPU混合颜色计算出来的,视图的layer一般在最下层,阴影则在视图layer之下。mask是layer的一个属性,它也是CALayer类型的,从官方对该属性的注释可知,默认情况下mask是为nil不存在的。mask相当于一个遮罩层,覆盖在视图的layer的上层,如果视图的layer是contentLayer,那么为这个layer添加一个mask,可以用mask来控制视图显示的区域和透明度。在mask的区域内的contentLayer会被显示,而之外的将不被显示,而区域内的contentLayer将通过mask层把像素颜色传递出去,如果mask的opacity不为1,那么mask将会按照opacity值过滤contentLayer的内容。当为视图设置了mask后,mask的复杂度会决定GPU的计算复杂度,当mask的opacity不为1时或者视图的alpha不为1,那么GPU将进行多层layer的混合颜色计算。

  • shadows(阴影)

阴影是直接合成一个在视图下面的layer,而不是在下面创建一个新的视图来当做阴影,当阴影的透明度不为1时,它的渲染复杂度会比较大。

  • EdgeAnntialiasing(抗锯齿)

allowsEdgeAntialiasing是ios7以后提供的方法,用来抗锯齿,有时候图片缩放或者界面旋转会造成边框出现锯齿。而锯齿的计算是非常耗性能的会造成离屏渲染的。所以在出现锯齿情况下allowsEdgeAntialiasing设置为YES

  • GroupOpacity(不透明)

allowsGroupOpacity是设置视图子视图在透明度上是否跟父视图一样,一般默认情况下是为YES的。如果父视图的透明度不为1,那么子视图的透明度也不会为1。在GPU渲染的时候,就会造成既要渲染子视图还要渲染子视图下面的父视图内容,然后合成视图。这样造成GPU计算复杂度增大需要离屏渲染解决。

  • 复杂形状比如圆角等

这里复杂形分为两种
一种是有系统设置造成的形状,比如设置圆角用maskToBundle加cornerRadius这种是有系统剪裁形成的圆角形状。
另一种是绘制生成的形状,比如图片中有圆角区域外是透明的或者直接绘制圆角。
系统形状会造成GPU的消耗,因为剪裁会很耗性能,而绘制会造成CPU性能消耗高,因为绘制工作是由CPU造成的

  • 渐变

渐变的渲染计算是非常负责好性能的。

  • Color Blended layers
    标示混合的图层会为红色,不透明的图层为绿色,通常我们希望绿色的区域越多越好。
    Color Hits Green and Misses Red
    假如我们设置viewlayer的shouldRasterize为YES,那些成功被缓存的layer会标注为绿色,反之为红色,下面会有详细介绍。
  • Color copied images
    标示那些被Core Animation拷贝的图片。这主要是因为该图片的色彩格式不能被GPU直接处理,需要在CPU这边做转换,假如在主线层做这个操作对性能会有一定的影响。
  • Color misaligned images
    被缩放的图片会被标记为黄色,像素不对齐则会标注为紫色。
  • Color offscreen-rendered yellow
    标示哪些layer需要做离屏渲染(offscreen-render)

从上面的点相信你已经了解到了造成离屏渲染的原因。
下面是关于离屏渲染、界面优化的方法

  • (1.)圆图:
/* 思路:用不透明的mask来实现仅显示圆角内区域。 之所以不采用异步绘制的方式是因为绘制会消耗CPU性能,而且绘制需要考虑是否缓存,如果不缓存每次都需要绘制很耗电,但基于负载平衡的原理,有时也可以采用绘制来减少GPU压力 */
 button.frame = CGRectMake(0, 0, 100, 100);
 button.backgroundColor = [UIColor redColor];
//显示路径,根据UIRectCorner枚举来控制那些区域需要圆角
 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 100*scale, 100*scale) byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(5, 5)];
        CAShapeLayer *mask = [[CAShapeLayer alloc] init];
        mask.path = path.CGPath;
        mask.frame = CGRectMake(0, 0, 100, 100);
        button.layer.mask = mask2;

本人在经过各种测试和观看各种文章资料后百思不得其解,从原理上来说上面指出的离屏渲染的几个点确实会造成离屏渲染。但是我代码测试并查看Color Off-Screen Rendered。居然没有高亮黄色。。。纳尼!!!难道是苹果又做了优化了。。。正在查找苹果文档

接下来说说Color Blender Layer,在模拟器中旋转Debug--> Color Blender Layer。模拟器界面中出现绿色的部分表示没有透明内容,红色的表示有透明内容。对于好的程序来说,绿色越多越好,上面离屏渲染讲过了,透明会造成GPU的计算复杂度变大,需要混合颜色计算。下面来说说解决这个问题的方法

  • (2.)UILabel: 中如果显示的文本是英文你会发现显示的是绿色,然而换成中文后居然显示的是红色。
/* 普通的label只需要根据界面需求设置个背景颜色设置maskToBundle为YES,而button中的label把背景颜色设置成跟按钮一个颜色设置maskToBundle为YES, */
label.background = [UIColor redColor];
label.maskToBundle = YES;
  • (3.)对于图片中有透明区域,这就需要根据界面与设计同学进行调整。虽然现在的处理器越来越强,这些优化微不足道,但对于一个合格的程序员而言,尽善尽美才是追求

  • (4.)异步加载绘制
    知识点:对象的创建,属性的调整等都比较消耗CPU,所以尽量的使用轻量级的对象可以减少CPU的消耗,而CALayer的量级比UIVIew轻许多。所以数据或对象的创建尽量放在异步线程中执行,保证主线程的畅通快速。但包含CALayer的控件都必须在主线程中创建操作,而界面控件一般都是在viewDidLoad里创建的,而系统方法都是在主线程中执行的,具体原因这里可以要说说Runloop的原理,过段时间写一篇关于Runloop原理的文章说明吧。

/*如果viewDidLoad内部代码执行耗时耗性会造成界面跳转显示卡顿,所以我采用异步主队列方式让控件的创建设置放在下一次MainRunloop的运行中,这样界面的跳转会很流畅。
*/
- (void)viewDidLoad
{
    [super viewDidLoad];
dispatch_async(dispatch_get_main_queue(), ^{
    /* alphaButton */
        self.alphaButton = [[UIButton alloc] init];
        self.alphaButton.frame = CGRectMake((V_width - 100*scale)/2, 100*scale, 100*scale, 100*scale);
        self.alphaButton.backgroundColor = [UIColor redColor];
        self.alphaButton.alpha = 0.5;
        [self.alphaButton setTitle:@"透明按钮" forState:UIControlStateNormal];
        [self.view addSubview:self.alphaButton];
  }):
}

(5.)界面的数据采用异步线程的方式去计算配置,当界面数据都配置完全了,在回到主线程中去设置UI
(6.)在很多时候界面的数据我会需要从网络中获取,而有时多个网络请求之间没有关联关系,我们可以采用信号量的方式,去同步请求网络数据,当所有网络数据都返回后,在开始计算配置数据

/* 创建信号量 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
   
   dispatch_async(dispatch_get_global_queue(0, 0), ^{
   //这里面是网络请求1
   //请求成功或者失败后需要去发送信号量,告诉等待队列已经完成一个任务的等待
   dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
   //这里面是网络请求2
 //请求成功或者失败后需要去发送信号量,告诉等待队列已经完成一个任务的等待
   dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
   //这里面是网络请求3
 //请求成功或者失败后需要去发送信号量,告诉等待队列已经完成一个任务的等待
   dispatch_semaphore_signal(semaphore);
});
       /* 有几个任务就创建对少个信号等待 */
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       /* 当网络数据都返回了,异步去配置计算界面最终显示需要的数据 */
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
       //配置计算界面最终显示需要的数据
       //数据配置完成后回到主线程更新UI  
     dispatch_async(dispatch_get_main_queue(), ^{
          //更新UI
       });
});

(7.)通过Storyboard创建的视图对象消耗的资源比纯代码创建对象要多很多
(8.)Block回调来异步执行任务回调(Block是个很神奇的东西,要灵活应用啊)

//文本的宽高计算会占用很大一部分资源,所以尽量用异步线程去执行操作,计算好后再回到主线程返回数据。
/**
 计算文字宽高
 
 @param string (NSString *) 计算高度的字符串
 @param maxHeight (CGFloat) 最大高度[如果最大高度为0,表示无高度限制]
 @param maxWidth (CGFloat) 最大宽度
 @param textFont (UIFont *) 文字粗细度
 @param block (CGSize) 返回文字的size
 */
+(void)textBoundingRectWithString:(NSString *)string maxHeight:(CGFloat)maxHeight maxWidth:(CGFloat)maxWidth textFont:(UIFont *)textFont Block:(void (^)(CGSize obj))block
{
    /* 如果传入内容有误,直接返回结果到当前线程*/
    if (!textFont || [self isBlankString:string] == YES) {
        if (block) {
            block(CGSizeMake(0, 0));
        }
      return;
    }
    /* 异步执行计算操作*/
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        CGSize lastSize;
        if (maxHeight == 0) {
            CGSize size = [string boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:textFont} context:nil].size;
            lastSize = CGSizeMake(ceilf(size.width), ceilf(size.height));
        }else
        {
            CGSize size = [string boundingRectWithSize:CGSizeMake(maxWidth, maxHeight) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:textFont} context:nil].size;
            lastSize = CGSizeMake(ceilf(size.width), ceilf(size.height));
        }
        /* 计算完成后再主线程中回调数据,因为一般拉倒值后会直接设置UI控件属性。 */
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block(lastSize);
            }
        });
    });
}
  • (9.)关于TableView的优化请看我另外一篇文章UITableView的性能优化

  • (10.)有次跟朋友讨论优化的时候,说道为什么微博内容多也复杂,流畅度这么高。我们改用的方法都用了,但是cell内部内容一复杂帧数就开始下降了。后来才知道,原来是自动布局的锅,再加上自己对文本内容认识深度不够。布局是非常好性能资源的,有时为了性能少用Autolayouer技术和UILabel(但实际情况好像不可能,哇咔咔)。那么选择一个好的自动布局第三方尤为重要了。微博可能是有一套非开源的布局方法吧(这里有个来自百度知道团队的开源项目可以看看代码学习学习:FDTemplateLayoutCell。)

  • (11.)图片的缩放,UIImageView的尺寸最好跟Bundle里的原图大小,因为图片的缩放是非常耗性能的。在实际开发中,需要适配不同的屏幕尺寸,这个时候就需要与设计大神们好好沟通了。我们常在开发适配的时候,会写一个比例尺寸,界面在不同屏幕下的尺寸都是按照这个比例缩放的。所以要把自己的比例告诉设计大神们才能达到不缩放。

/* 这是我常用的比例 */
#define scale MIN([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)/375.f

如果还是会缩放,那么你就需要异步去把图片绘制成UIImageView大小的图片了

/**
 根据边距拉伸图片
 
 @param sourceImage 原图片
 @param edgeInsets 边距
 @param resizingMode 缩放模式
 @param size 需要拉伸的大小
 @param block 处理后的图片
 */
+(void)imageCompress:(UIImage *)sourceImage forEdgeInsets:(UIEdgeInsets)edgeInsets resizingMode:(UIImageResizingMode)resizingMode forSize:(CGSize)size Block:(void (^)(UIImage *image))block
{
  /*异步处理*/
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        UIImage *Image;
        Image = [sourceImage resizableImageWithCapInsets:edgeInsets resizingMode:resizingMode];
        UIGraphicsBeginImageContext(CGSizeMake(size.width, size.height));
        [Image drawInRect:CGRectMake(0,0,size.width,  size.height)];
        UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        if (block) {
          /* 回到主线程 */
            dispatch_async(dispatch_get_main_queue(), ^{
                block(newImage);
            });
        }
    });
}
  • (12.)避免不必要的图片缓存:通常我们会用imageNamed:来加载图片,但是这个方法会对图片进行缓存。对于一些只有特定界面才有不常用的图片用这个方法会造成一定的内存消耗,一般不常用的图片采用initWithContentsOfFile:。可以自己写一个UIImage的类别,自行判断使用哪一个方法。这个方法我有用过,但可能目前处理器性能太好或者设计同学的图片本身就很小,内存上并看不出多大差别。对于那些图片为主的App这个方法还是很有用的

  • (13.)减少文件读取次数:文件的读取是是否消耗资源的,所以在没有必要分开文件内容的情况下,尽量把内容放在一个文件中,减少消耗。例如图片的读取,第一种,多个标签图片放在一个图片中,然后根据图片进行区域绘制,这样就减少了对图片的读取时消耗CPU的性能,第二种shouldRasterize光栅化,在GPU渲染时,直接取出上次的绘制内容,来减少文件的读取和重新绘制。


未完待续...

后期还有很多优化内容比如缓存数据等等

参考文章:
离屏渲染优化详解:实例示范+性能测试
iOS 保持界面流畅的技巧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343