你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 一次對MKMapView的性能優化

一次對MKMapView的性能優化

編輯:IOS開發基礎

作者:裡脊串 授權本站轉載。

前言

最近做的項目主要是LBS這塊 主打成員定位功能 我們的UI設計是這樣的

pic_001.png

乍一看上去是挺好挺美觀的 不同的人會顯示不同的頭像 可是當人扎堆的時候 問題就來了

pic_002.png

當人多的時候(例如上圖所示) 地圖滑動起來就能感覺到明顯頓卡 那種不流暢感能折磨死人 所以 自然我們要解決這個問題(等等 先不要吐槽為什麼不用地圖聚合 因為這已經是地圖放到最大了 聚合不適合這次的問題討論)

分析

首先看下我是怎麼實現這個annotationView的 由於這個annotationsView是異形的(也就是無法通過設置圓角直接得到) 而且裡面的圖片還因用戶而異 所以解決方案就是使用layer.mask來進行遮罩 代碼如下

@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設置頭像就行了

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

接下來用工具分析一下問題出來哪 分析性能當然是選擇Instrments(用法在這裡就不做介紹了) 打開Core Animation 然後運行程序 滑動地圖 可以看到性能分析如下

pic_003.png

原來平均幀數只有不到30幀 這離我們的目標60幀差得實在太遠

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

pic_004.png

由於MKMapView的原因 這裡我們主要關心這幾個選項

  • Color Blended Layers

  • Color Misaligned Images

  • Color Offscreen-Rendered Yellow

分別打開這幾個選項 結果如下

pic_005.png

可以看到

  • 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表現如何

pic_006.png

  • Color Blended Layers全中 這也是無可避免的 因為顯示的就是一張帶透明度的圖 但是由於地圖的特殊性(頭像的位置變化間隔較長 所以不會經常引發合成 也沒有動畫) 所以這裡也不是問題

  • Color Misaligned Images沒問題了 因為頭像已被縮放成了相同大小

  • Color Offscreen-Rendered Yellow沒問題了 因為只是簡單的顯示了一張圖片 而並沒有需要離屏渲染的東西了

再來看下幀數情況

pic_007.png

Oh-Yeah~ 不光幀數達到了我們的目標60幀(由於還有業務邏輯線程在後台跑 所以沒有那麼的穩定) 就連平均運行耗時都下降了不少 就算地圖上再多顯示幾十個人 也不成問題了

小結

不光是MKMapView 其實包括UITableView在內的很多地方都可以用文中所說的方法去優化 其核心點就是 合成+緩存 當然 由於合成還是會耗費一部分資源的 所以比較適合頭像這種小的資源

關於圖形性能優化 可以看下這篇好文(有對文中提到的Debug Option不太明白的 這裡有詳細的解釋)

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved