0%

记Xcode11后[UIImage imageNamed:]导致启动卡住问题跟踪

团队全部切换到Xcode11环境后,观察到广告页的倒计时停着不动跟着会忽然跳到首页,以下记录问题排查过程。

环境:Xcode11.3 + iOS10 ~ iOS13

定位问题

根据git log排除启动流程导致的主线程阻塞(以前发生过),掏出Debug神器Instrments,调试结果如下:
imageNamed
能看出大部分的CPU时间是耗在[UIImage imageNamed:]上。

解决问题

把启动流程中调用imageNamed的地方耗时打印出来,大概是25ms左右,造成卡顿的原因是启动中需要加载两个60+图片的小动画,这样延时大概就有3s+以上了。问题找到了,但[UIImage imageNamed:]是系统方法,无法通过修改实现的方式来解决,思考可通过以下两种方式修复:

  1. 使用Xcode10编译,经验证imageNamed耗时在Xcode10上只有1ms左右,但团队需要适配暗黑模式,已经无法回头了;
  2. 按需加载这些图片,但这只是把卡顿的时机变成的显示图片时,不可取;
    两个方法都不可取,思考一下imageNamed实际上就是做了图片解码和缓存两个工作,也许苹果在Xcode11上手残把imageNamed的实现从异步不小心改成了同步。因为赶着上线发版,干脆自己coding把图片解码和缓存都做了并替换了启动流程的图片加载,直接上代码:
    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
    + (UIImage *)loadImageFromCache:(NSString *)imageName {
    NSString *key = [NSString stringWithFormat:@"vs_heller_%@", imageName];
    UIImage *image = [[SDWebImageManager sharedManager].imageCache imageFromCacheForKey:key];
    return image;
    }

    + (void)storeImageFromCache:(NSString *)imageName image:(UIImage *)image {
    NSString *key = [NSString stringWithFormat:@"vs_heller_%@", imageName];
    [[SDWebImageManager sharedManager].imageCache storeImage:image forKey:key completion:nil];
    }

    + (void)forceDecodedImageWithImageName:(NSString *)imageName block:(void(^)(UIImage *image))block {
    UIImage *image = [self loadImageFromCache:imageName];
    if (image) {
    if(block) {
    block(image);
    }
    } else {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    @autoreleasepool {
    UIImage *image = [[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAutomatic];
    image = [UIImage decodedImageWithImage:image];
    [self storeImageFromCache:imageName image:image];

    dispatch_async(dispatch_get_main_queue(), ^{
    if(block) {
    block(image);
    }
    });
    }
    });
    }
    }

    + (void)forceDecodedImageWithImageName:(NSString *)imageName block:(void(^)(UIImage *image))block {
    UIImage *image = [self loadImageFromCache:imageName];
    if (image) {
    if(block) {
    block(image);
    }
    } else {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    @autoreleasepool {
    UIImage *image = [[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAutomatic];
    image = [UIImage decodedImageWithImage:image];
    [self storeImageFromCache:imageName image:image];

    dispatch_async(dispatch_get_main_queue(), ^{
    if(block) {
    block(image);
    }
    });
    }
    });
    }
    }

这里利用了SDWebImage的强制解码代码和复用了它的cache,验证有效,可恢复原有的启动速度。

深究

版本发布后,进一步追查问题,有以下测试

  1. 用Xcode11新建Demo测试imageNamed耗时只有1ms左右;
  2. 对比工程和Demo的build setting并没有差异;
  3. 移除assets的–optimization space编译选项,结果没有差异;
  4. 主工程把iOS Deployment Target设为13.2,发现卡顿问题修复,逐步把最低系统要求设为< 9.3后问题复现,得出结论是iOS Deployment Target >= 9.3可修复问题;