关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » iOS开发 »iOS开发之MKMapView的性能优化

    iOS开发之MKMapView的性能优化

    2015-07-18 00:00:00 出处:杰瑞教育
    分享

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

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

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

    分析

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

    @implementation MMAnnotationView
    
    - (instancetype)initWithAnnotation:(id<MKAnnotation>)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设置头像就行了

    [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了吗 试试看

    - (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了

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

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

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

    再来看下帧数情况

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

    小结

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

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

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    帝国CMS7.5编辑器上传图片取消宽高的三种方法

    帝国cms如何自动生成缩略图的实现方法

    Windows 12即将到来,将彻底改变人机交互

    帝国CMS 7.5忘记登陆账号密码怎么办?可以phpmyadmin中重置管理员密码

    帝国CMS 7.5 后台编辑器换行,修改回车键br换行为p标签

    Windows 11 版本与 Windows 10比较,新功能一览

    Windows 11激活产品密钥收集及专业版激活方法

    如何从 Windows 11 中完全删除/卸载 OneNote?无解!

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    IT头条

    华为Pura80系列新机预热,余承东力赞其复杂光线下的视频拍摄实力

    01:28

    阿里千问3开源首战告捷:全球下载破千万,国产AI模型崛起新高度!

    01:22

    DeepSeek R1小版本试升级:网友实测编程能力已达到国际一线水平

    23:15

    NVIDIA 与 Dell 合作,大规模交付 Blackwell AI 系统

    20:52

    Cerebras 以最快的 Llama 4 Maverick 性能引领 LLM 推理竞赛

    20:51

    技术热点

    PHP中的随机性——你觉得自己幸运吗?

    搞定Ubuntu Linux下WPA无线上网

    Java使用内存映射实现大文件的上传

    MySQL安全性指南

    MySQL两项性能的基本测试浅谈

    教您使用UniqueIdentifier选取SQL Server主键

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。