iOS性能优化---转载《三》一次对MKMapView的性能优化(instrments core animation 使用)

本文主要是介绍iOS性能优化---转载《三》一次对MKMapView的性能优化(instrments core animation 使用),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

最近做的项目主要是LBS这块 主打成员定位功能 我们的UI设计是这样的


乍一看上去是挺好挺美观的 不同的人会显示不同的头像 可是当人扎堆的时候 问题就来了


当人多的时候(例如上图所示) 地图滑动起来就能感觉到明显顿卡 那种不流畅感能折磨死人 所以 自然我们要解决这个问题(等等 先不要吐槽为什么不用地图聚合 因为这已经是地图放到最大了 聚合不适合这次的问题讨论)

分析

首先看下我是怎么实现这个annotationView的 由于这个annotationsView是异形的(也就是无法通过设置圆角直接得到) 而且里面的图片还因用户而异 所以解决方案就是使用layer.mask来进行遮罩 代码如下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

@implementation MMAnnotationView

- (instancetype)initWithAnnotation:(id)annotation reuseIdentifier:(NSString *)reuseIdentifier

{

    self = [super initWithAnnotation:annotation reuseIdentifier:reuseIdentifier];

    if ( self )

    {

        self.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height);

        self.centerOffset = CGPointMake(0, -(TRACK_ANNOTATION_SIZE.height-3)/2);

        self.canShowCallout = NO;

        self.avatarView = [[UIImageView alloc] initWithFrame:self.bounds];

        [self addSubview:self.avatarView];

        self.avatarView.contentMode = UIViewContentModeScaleAspectFill;

        CAShapeLayer *shapelayer = [CAShapeLayer layer];

        shapelayer.frame = self.bounds;

        shapelayer.path = self.framePath.CGPath;

        self.avatarView.layer.mask = shapelayer;

        self.layer.shadowPath = self.framePath.CGPath;

        self.layer.shadowRadius = 1.0f;

        self.layer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor;

        self.layer.shadowOpacity = 1.0f;

        self.layer.shadowOffset = CGSizeMake(0, 0);

        self.layer.masksToBounds = NO;

    }

    return self;

}

//mask路径

- (UIBezierPath *)framePath

{

    if ( !_framePath )

    {

        CGFloat arrowWidth = 14;

        CGMutablePathRef path = CGPathCreateMutable();

        CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetWidth(self.bounds)), 3,3);

        CGPoint p[3] = {

        {CGRectGetMidX(self.bounds)-arrowWidth/2, CGRectGetWidth(self.bounds)-6},

        {CGRectGetMidX(self.bounds)+arrowWidth/2, CGRectGetWidth(self.bounds)-6},

        {CGRectGetMidX(self.bounds), CGRectGetHeight(self.bounds)-4}

        };

        CGPathAddRoundedRect(path, NULL, rectangle, 5, 5);

        CGPathAddLines(path, NULL, p, 3);

        CGPathCloseSubpath(path);

        _framePath = [UIBezierPath bezierPathWithCGPath:path];

        CGPathRelease(path);

    }

    return _framePath;

}

我用代码生成了形状路径 并以此生成了layer的mask和shadowPath

使用时 只要直接用SDWebImage设置头像就行了

1

[annotationView.avatarView sd_setImageWithURL:[NSURL URLWithString:avatarURL] placeholderImage:placeHolderImage];

接下来用工具分析一下问题出来哪 分析性能当然是选择Instrments(用法在这里就不做介绍了) 打开Core Animation 然后运行程序 滑动地图 可以看到性能分析如下


原来平均帧数只有不到30帧 这离我们的目标60帧差得实在太远

再使用Debug Option来深入分析一下


由于MKMapView的原因 这里我们主要关心这几个选项

  • Color Blended Layers
  • Color Misaligned Images
  • Color Offscreen-Rendered Yellow

分别打开这几个选项 结果如下


可以看到

  • Color Blended Layers没有问题 不过这也是正常的 由于使用了mask 没有透明的地方
  • Color Misaligned Images除了默认头像外全中 这是因为服务器上的图片大小跟显示的大小不一致 导致缩放 而默认头像则是一致的 所以没问题
  • Color Offscreen-Rendered Yellow全中 由于使用了mask 导致大量的离屏渲染 这也是性能下降的主要原因

解决

问题的原因找到了 那么接下来该如何解决呢?

  • 首先mask是肯定不能用了
  • 其次下载下来的图片我们要预处理成实际大小

那么 直接把下载下来的图片合成为我们要显示的最终结果不就ok了吗? 试试看

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

- (void)loadAnnotationImageWithURL:(NSString*)url imageView:(UIImageView*)imageView

{

    //将合成后的图片缓存起来

    NSString *annoImageURL = url;

    NSString *annoImageCacheURL = [annoImageURL stringByAppendingString:@"cache"];

    UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:annoImageCacheURL];

    if ( cacheImage )

    {

        //LLLog(@"hit cache");

        imageView.image = cacheImage;

    }

    else

    {

        //LLLog(@"no cache");

        [imageView sd_setImageWithURL:[NSURL URLWithString:annoImageURL]

        placeholderImage:placeHolderImage

        completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {

        if (!error)

        {

            UIImage *annoImage = [image annotationImage];

            imageView.image = annoImage;

            [[SDImageCache sharedImageCache] storeImage:annoImage forKey:annoImageCacheURL];

            }

        }];

    }

}

@implementation UIImage (LJC)

- (UIImage*) annotationImage

{

    static UIView *snapshotView = nil;

    static UIImageView *imageView = nil;

    if ( !snapshotView )

    {

        snapshotView = [UIView new];

        snapshotView.frame = CGRectMake(0, 0, TRACK_ANNOTATION_SIZE.width, TRACK_ANNOTATION_SIZE.height);

        imageView = [UIImageView new];

        [snapshotView addSubview:imageView];

        imageView.clipsToBounds = YES;

        imageView.frame = snapshotView.bounds;

        imageView.contentMode = UIViewContentModeScaleAspectFill;

        CGFloat arrowWidth = 14;

        CGMutablePathRef path = CGPathCreateMutable();

        CGRect rectangle = CGRectInset(CGRectMake(0, 0, CGRectGetWidth(imageView.bounds), CGRectGetWidth(imageView.bounds)), 3,3);

        CGPoint p[3] = {

            {CGRectGetMidX(imageView.bounds)-arrowWidth/2, CGRectGetWidth(imageView.bounds)-6},

            {CGRectGetMidX(imageView.bounds)+arrowWidth/2, CGRectGetWidth(imageView.bounds)-6},

            {CGRectGetMidX(imageView.bounds), CGRectGetHeight(imageView.bounds)-4}

        };

        CGPathAddRoundedRect(path, NULL, rectangle, 5, 5);

        CGPathAddLines(path, NULL, p, 3);

        CGPathCloseSubpath(path);

        CAShapeLayer *shapelayer = [CAShapeLayer layer];

        shapelayer.frame = imageView.bounds;

        shapelayer.path = path;

        imageView.layer.mask = shapelayer;

        snapshotView.layer.shadowPath = path;

        snapshotView.layer.shadowRadius = 1.0f;

        snapshotView.layer.shadowColor = [UIColor colorWithHex:0x666666FF].CGColor;

        snapshotView.layer.shadowOpacity = 1.0f;

        snapshotView.layer.shadowOffset = CGSizeMake(0, 0);

        CGPathRelease(path);

    }

    imageView.image = self;

    UIGraphicsBeginImageContextWithOptions(TRACK_ANNOTATION_SIZE, NO, 0);

    [snapshotView.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage *copied = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return copied;

}

@end

然后使用的时候 只要简单的如下调用就OK了

1

[self loadAnnotationImageWithURL:avatarURL imageView:annotationView.avatarView];

看看修改之后的Instruments表现如何


  • Color Blended Layers全中 这也是无可避免的 因为显示的就是一张带透明度的图 但是由于地图的特殊性(头像的位置变化间隔较长 所以不会经常引发合成 也没有动画) 所以这里也不是问题
  • Color Misaligned Images没问题了 因为头像已被缩放成了相同大小
  • Color Offscreen-Rendered Yellow没问题了 因为只是简单的显示了一张图片 而并没有需要离屏渲染的东西了

再来看下帧数情况


Oh-Yeah~ 不光帧数达到了我们的目标60帧(由于还有业务逻辑线程在后台跑 所以没有那么的稳定) 就连平均运行耗时都下降了不少 就算地图上再多显示几十个人 也不成问题了

小结

不光是MKMapView 其实包括UITableView在内的很多地方都可以用文中所说的方法去优化 其核心点就是 合成+缓存 当然 由于合成还是会耗费一部分资源的 所以比较适合头像这种小的资源

关于图形性能优化 可以看下这篇好文(有对文中提到的Debug Option不太明白的 这里有详细的解释)




原文地址:http://www.cocoachina.com/ios/20150717/12583.html

这篇关于iOS性能优化---转载《三》一次对MKMapView的性能优化(instrments core animation 使用)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/405878

相关文章

使用Python构建一个Hexo博客发布工具

《使用Python构建一个Hexo博客发布工具》虽然Hexo的命令行工具非常强大,但对于日常的博客撰写和发布过程,我总觉得缺少一个直观的图形界面来简化操作,下面我们就来看看如何使用Python构建一个... 目录引言Hexo博客系统简介设计需求技术选择代码实现主框架界面设计核心功能实现1. 发布文章2. 加

shell编程之函数与数组的使用详解

《shell编程之函数与数组的使用详解》:本文主要介绍shell编程之函数与数组的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录shell函数函数的用法俩个数求和系统资源监控并报警函数函数变量的作用范围函数的参数递归函数shell数组获取数组的长度读取某下的

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Python虚拟环境终极(含PyCharm的使用教程)

《Python虚拟环境终极(含PyCharm的使用教程)》:本文主要介绍Python虚拟环境终极(含PyCharm的使用教程),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录一、为什么需要虚拟环境?二、虚拟环境创建方式对比三、命令行创建虚拟环境(venv)3.1 基础命令3

Python Transformer 库安装配置及使用方法

《PythonTransformer库安装配置及使用方法》HuggingFaceTransformers是自然语言处理(NLP)领域最流行的开源库之一,支持基于Transformer架构的预训练模... 目录python 中的 Transformer 库及使用方法一、库的概述二、安装与配置三、基础使用:Pi

关于pandas的read_csv方法使用解读

《关于pandas的read_csv方法使用解读》:本文主要介绍关于pandas的read_csv方法使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录pandas的read_csv方法解读read_csv中的参数基本参数通用解析参数空值处理相关参数时间处理相关

使用Node.js制作图片上传服务的详细教程

《使用Node.js制作图片上传服务的详细教程》在现代Web应用开发中,图片上传是一项常见且重要的功能,借助Node.js强大的生态系统,我们可以轻松搭建高效的图片上传服务,本文将深入探讨如何使用No... 目录准备工作搭建 Express 服务器配置 multer 进行图片上传处理图片上传请求完整代码示例

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

Python中使用正则表达式精准匹配IP地址的案例

《Python中使用正则表达式精准匹配IP地址的案例》Python的正则表达式(re模块)是完成这个任务的利器,但你知道怎么写才能准确匹配各种合法的IP地址吗,今天我们就来详细探讨这个问题,感兴趣的朋... 目录为什么需要IP正则表达式?IP地址的基本结构基础正则表达式写法精确匹配0-255的数字验证IP地

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求