百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

一、前言

之前的文章介绍了图片优化和代码优化的几种办法,本篇文章要点介绍HEIC图片和无用类检测的优化实践。HEIC是High Efficiency Image Format(高效图画格局)的缩写,是一种新的图画文件格局,它是2017年苹果公司在iOS 11中引进,用于代替JPEG图画格局,以更高效地紧缩图画并减少存储空间占用。HEIC支撑多帧图画、通明度和16位深度色彩,使得它成为高质量图画和动画的理想挑选。本文要点探究HEIC图片在百度APP中运用的可行性和包体积收益,验证HEIC图片在Bundle和Asset Catalog的兼容性,要点研讨了Asset Catalog办理图片的机制,记载了验证过程中发现的特别问题和解决思路。无用类则是详细介绍了如何用静态剖析和动态剖析相结合的办法,精简代码体积。

百度APP iOS端包体积优化实践系列文章回顾:

《百度APP iOS端包体积50M优化实践(一)总览》

《百度APP iOS端包体积50M优化实践(二) 图片优化》

《百度APP iOS端包体积50M优化实践(三) 资源优化》

《百度APP iOS端包体积50M优化实践(四) 代码优化》

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

二、HEIC图片格局转化和运用办法

2.1 格局转化

有三种常见的HEIC图片转化办法:Mac图片转化功用、Mac自带sips指令、多平台支撑的ImageMagick指令。

2.1.1 Mac图片转化功用:

  1. 右键图片,快速操作—>转化图画

  2. 格局选HEIF,图画巨细依据需求挑选

图片转存失利,主张直接上传图片文件

2.1.2 sips东西:

sips是一个MacOS自带的指令行的图片处理东西,具有转化图片格局、修正图片巨细(扩充或者从头采样缩小图片)、修正质量,设置版权信息等功用。

举例:sips -s format heic -s formatOptions default guideview@3x.png –out guideview@3x.heic

2.1.3 ImageMagick东西:

ImageMagick 包括一个用于履行复杂图画处理任务的指令行界面,以及用于将其功用集成到软件使用程序中的 API。它是用 C 编写的,能够在各种操作体系上运用,包括 Linux、Windows 和 macOS。

用法参考:

imagemagick.org/index.php

需求手动装置:

brewinstallimagemagick

convertguid‍eview@3x.pngguideview@3x.heic

2.2 HEIC在iOS中运用

在iOS体系中,ImageIO、Core Image、UIKit、PhotoKit都支撑HEIC图片。HEIC图片能够放Bundle里也能够放Asset Catalog里。运用原生办法就能够创立UIImage目标,和JPEG、PNG等图片运用办法共同。

// 加载本地图片
UIImage *image = [UIImage imageNamed:@"heifFileName"];
UIImage *image = [UIImage imageWithContentsOfFile:filePath];
// 由 网络请求的 NSData 解码
UIImage *image = [UIImage imageWithData:heifImageData];

2.3 HEIC图片兼容性

编码

硬编:A10及以上芯片 iOS 设备(iPhone7)

解码:

硬解:A9 及以上芯片 iOS 设备(iPhone6s),装备 6 代及以上 Inter Core 处理(Skylake)。

软解:iOS12 和 macOS 支撑软解码,(官方说是iOS11,实测iOS11并不能解码)

能够调用ImageI/O相关函数获取支撑的图片编解码支撑的格局,这儿值得留意的是,在iPhone6p,iOS11.0.4实测不支撑HEIC,即调用CGImageSourceCopyTypeIdentifiers();查询可解码格局包括public.heic,仍旧是无法正常显现出HEIC图片;在iPhone6p,iOS12.5.6测验机上能够正常显现HEIC图片。

//获取所支撑的图片格局数组,解码
CFArrayRef decodeArr = CGImageSourceCopyTypeIdentifiers();
NSArray *decodeUTI = (__bridge NSArray *)decodeArr;
NSLog(@"解码支撑%@", decodeUTI);
//获取所支撑的图片格局数组,编码
CFArrayRef encodeArr = CGImageDestinationCopyTypeIdentifiers();
NSArray *encodeUTI = (__bridge NSArray *)encodeArr;
NSLog(@"编码支撑%@", encodeUTI);

百度APP最低支撑的体系版别是iOS10,iOS10的5s和iPhone6、iPhone6p无法直接解码HEIC图片,这三款机型会受到影响。可是这并不意味着这些机型不能兼容HEIC图片。惯例思路是引进三方SDK,如:SDWebImageHEIFCoder(github.com/SDWebImage/… ,添加解码支撑,不过引进三方SDK变相添加了包体积,捉襟见肘。在测验中发现,将HEIC图片放入Asset Catalog办理,是能够在上述三款机型上正常显现图片的。

三、Bundle和Asset Catalog的兼容性

在iOS体系中,APP内的图片资源能够放Bundle和Asset Catalog。若图片放Bundle中,ipa包装置到设备上后,图片占用的磁盘空间和图片实践巨细共同。不过放Bundle的缺点是需求针对不同分辨率的进行放不同巨细的倍图,明显添加了包体积。

苹果引荐运用Asset Catalog办理内置资源,包括图片资源、音视频等,同样也支撑HIEC图片。Asset Catalog的优点明显易见,支撑app slicing、支撑设置拉伸区域、给不同的机型配置不同的图片、配置渲染色彩等。终究一切的文件终究会打包成.car紧缩文件。

对此,咱们挑选了两张具有代表性的图片,log.png是带有Alpha通道的图片和guideview@3x.png是不带Alpha通道的图片。然后分别生成对应的HEIC图片log.heic和guideview@3x.heic,图片没有通过任何其他紧缩处理。

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△guideview@3x.png

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△log.png

3.1 生成car文件

从Xcode编译的log中发现,体系运用自有actool东西对workspace内一切的.xcassets紧缩生成一个.car文件,关于Xcode是否衔接测验机,编译Assets.car的参数又有所不同。

关于未衔接测验机,挑选Any iOS Device(arm64),生成通用的Assets.car文件,编译参数如下:

// Any iOS Device(arm64)
/Applications/Xcode.app/Contents/Developer/usr/bin/actool--output-formathuman-readable-text--notices--warnings--export-dependency-info/Users/xxxxx/Library/Developer/Xcode/DerivedData/ImageDemoS-auwsocxqgbwbgmfoiguzuahzizre/Build/Intermediates.noindex/ImageDemoS.build/Debug-iphoneos/ImageDemoS.build/assetcatalog_dependencies--output-partial-info-plist/Users/xxxxx/Library/Developer/Xcode/DerivedData/ImageDemoS-auwsocxqgbwbgmfoiguzuahzizre/Build/Intermediates.noindex/ImageDemoS.build/Debug-iphoneos/ImageDemoS.build/assetcatalog_generated_info.plist--app-iconAppIcon--accent-colorAccentColor--compress-pngs--enable-on-demand-resourcesYES--development-regionen--target-deviceiphone--target-deviceipad--minimum-deployment-target16.0--platformiphoneos--compile/Users/xxxxx/Library/Developer/Xcode/DerivedData/ImageDemoS-auwsocxqgbwbgmfoiguzuahzizre/Build/Products/Debug-iphoneos/ImageDemoS.app/Users/xxxxxR/baidu/personal-code/ImageDemoS/ImageDemoS/Assets.xcassets/Users/xxxxx/baidu/personal-code/ImageDemoS/Media.xcassets

若衔接了测验机,则依据测验机的机型和体系生成对应的Assets.car文件。要害参数–filter-for-thinning-device-configuration iPhone7,1 –filter-for-device-os-version 11.4.1,这两个参数能够解释HEIC图片在iOS11的iPhone6p上的兼容性问题。实测中发现HEIC图片放Asset Catalog中,实践上是能够在iOS11的iPhone6p上显现的,只不过这时候Asset Catalog里的图片现已不是HEIC编码了。详细的编译参数如下:

// iPhone6p(iOS11.4.1)
/Applications/Xcode.app/Contents/Developer/usr/bin/actool--output-formathuman-readable-text--notices--warnings--export-dependency-info/Users/xxxxx/Library/Developer/Xcode/DerivedData/ImageDemoS-auwsocxqgbwbgmfoiguzuahzizre/Build/Intermediates.noindex/ImageDemoS.build/Debug-iphoneos/ImageDemoS.build/assetcatalog_dependencies--output-partial-info-plist/Users/xxxxx/Library/Developer/Xcode/DerivedData/ImageDemoS-auwsocxqgbwbgmfoiguzuahzizre/Build/Intermediates.noindex/ImageDemoS.build/Debug-iphoneos/ImageDemoS.build/assetcatalog_generated_info.plist--app-iconAppIcon--accent-colorAccentColor--compress-pngs--enable-on-demand-resourcesYES--optimizationspace--filter-for-thinning-device-configurationiPhone7,1--filter-for-device-os-version11.4.1--development-regionen--target-deviceiphone--target-deviceipad--minimum-deployment-target9.0--platformiphoneos--compile/Users/xxxxx/Library/Developer/Xcode/DerivedData/ImageDemoS-auwsocxqgbwbgmfoiguzuahzizre/Build/Products/Debug-iphoneos/ImageDemoS.app/Users/xxxxx/baidu/personal-code/ImageDemoS/ImageDemoS/Assets.xcassets/Users/xxxxx/baidu/personal-code/ImageDemoS/Media.xcassets

一起咱们发现,运用actool东西对.xcassets进行处理时,会呈现以下warning,而不是error。从actool给的警告信息看,HIEC图片只在iOS11以后的体系上被支撑,可是在生成Asset.car文件时,actool东西会依据指定的最小体系版别,对iOS11以下的机型生成兼容的图片,尽管图片体积或许有所变大,可是HIEC图片在Asset Catalog中对一切机型兼容。而HIEC图片放Bundle则无法兼容iOS11以下体系。进一步证明了actool自己对HEIC图片兼容性的处理。

/* com.apple.actool.document.warnings */
/Media.xcassets:./logHEICAlpha.imageset/[universal][][][3x][][][][][][][][][][]: warning: You're targeting iOS 9.0, but HEIF files can only be accessed from an Asset Catalog in iOS 11.0 and later.

3.2 解析car文件

解析Assets.car文件,能够运用Mac自带东西assetutil,能够移除通用的Assets.car里不需求的图片,也能够解析Assets.car的详细内容。也能够运用Asset Catalog Tinkerer显现图片,参考:github.com/insidegui/A… 在此咱们运用assetutil对Assets.car内容进行解析。指令如下:

assetutil -I Assets.car > Assets.json

下面的表格中关于通用Assets.car里的文件信息进行剖析,首要需求了解以下几个字段的含义:

SizeOnDisk:这是图片在Assets.car里实践的体积
Encoding:编码办法,HEIF便是HEIC图片的编码办法
Compression:紧缩算法

咱们能够得到以下定论:

1、PNG图片转HEIC图片体积会有所下降;

2、PNG图片和HEIC图片通过actool处理后,car文件里的图片巨细和实践巨细不共同;

3、car文件里图片巨细包体积和编码办法和紧缩算法相关,PNG和HEIC图片的终究巨细以SizeOnDisk字段数据为准;

4、actool会对图片做iOS体系和设备兼容,在不支撑HEIC的设备上会将HEIC图片转为其他能够显现的格局。

图片巨细:15,444(log.png)10,867(log.heic)

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

图片巨细:72,547(guideview@3x.png)33,589(guideview@3x.heic)

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

四、Alpha通道兼容性问题

在实践操作过程中,咱们发现某些带有alpha通道的PNG图片在转HEIC图片后,在iOS11、iOS12、iOS13体系上会呈现图片无法显现、显现为白色、绿色等各种问题,可是也有一部分带alpha通道的图片显现完全正确。针对此类问题咱们做了一系列的探索,终究确认问题原因。一切被pngquant:pngquant.org 有损(60-90)紧缩过的带有alpha通道的图片,转化成HEIC图片后会呈现上述问题。以下是详细的剖析过程和相关数据。

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△图片显现为白色

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△通明显现为绿色

4.1 问题剖析思路

首要有几个问题需求考虑:

1、为什么同一张HIEC图片,iOS14和iOS15显现正常,而iOS11、iOS12、iOS13会呈现问题?

2、为什么同样是带Alpha通道的HEIC图片,有的会在iOS11、iOS12、iOS13体系上呈现问题,有的图片在一切体系都能够正常显现?

第一步:确认图片编码解码数据,将PNG和问题HEIC图片转Bitmap,查看RGBA值

上述问题只在带有alpha通道的部分HEIC图片上呈现,首要从编码和解码两个视点剖析alpha通道HIEC图片色彩失真问题。iOS设备上一切的图片都会先解码生成Bitmap位图,然后渲染成图片,所以需求获取一张图片的Bitmap数据和图片信息。获取Bitmap数据十分要害的一个结构体是CGImageRef, CGImageRef常见的有三种获取办法:

  1. UIKit供给放UIImage的CGImage特点,这是最常用的办法;

  2. ImageI/O供给的CGImageSourceCreateImageAtIndex 函数,这种适用于从文件解析图片;

  3. Core graphics供给的CGBitmapContextCreateImage,这种适用于已知bitmap graphics context情况下运用;

此处直接从UIImage获取CGImageRef。

/// 获取图片信息和像素
/// - Parameters:
///   - image: <#image description#>
 -(void)dumpImageInfo:(UIImage *)image
{
    // 获取CGImageRef
    CGImageRef cgimage = image.CGImage;
    size_t width  = CGImageGetWidth(cgimage);
    size_t height = CGImageGetHeight(cgimage);
    size_t bpr = CGImageGetBytesPerRow(cgimage);
    size_t bpp = CGImageGetBitsPerPixel(cgimage);
    size_t bpc = CGImageGetBitsPerComponent(cgimage);
    size_t bytes_per_pixel = bpp / bpc;
    CGBitmapInfo info = CGImageGetBitmapInfo(cgimage);
    NSLog(
        @"\n"
//        "===== %@ =====\n"
        "CGImageGetHeight: %d\n"
        "CGImageGetWidth:  %d\n"
        "CGImageGetColorSpace: %@\n"
        "CGImageGetBitsPerPixel:     %d\n"
        "CGImageGetBitsPerComponent: %d\n"
        "CGImageGetBytesPerRow:      %d\n"
        "CGImageGetBitmapInfo: 0x%.8X\n"
        "  kCGBitmapAlphaInfoMask     = %s\n"
        "  kCGBitmapFloatComponents   = %s\n"
        "  kCGBitmapByteOrderMask     = %s\n"
        "  kCGBitmapByteOrderDefault  = %s\n"
        "  kCGBitmapByteOrder16Little = %s\n"
        "  kCGBitmapByteOrder32Little = %s\n"
        "  kCGBitmapByteOrder16Big    = %s\n"
        "  kCGBitmapByteOrder32Big    = %s\n",
//        file,
        (int)width,
        (int)height,
        CGImageGetColorSpace(cgimage),
        (int)bpp,
        (int)bpc,
        (int)bpr,
        (unsigned)info,
        (info & kCGBitmapAlphaInfoMask)     ? "YES" : "NO",
        (info & kCGBitmapFloatComponents)   ? "YES" : "NO",
        (info & kCGBitmapByteOrderMask)     ? "YES" : "NO",
        (info & kCGBitmapByteOrderDefault)  ? "YES" : "NO",
        (info & kCGBitmapByteOrder16Little) ? "YES" : "NO",
        (info & kCGBitmapByteOrder32Little) ? "YES" : "NO",
        (info & kCGBitmapByteOrder16Big)    ? "YES" : "NO",
        (info & kCGBitmapByteOrder32Big)    ? "YES" : "NO"
    );
    // 获取位图数据
    CGDataProviderRef provider = CGImageGetDataProvider(cgimage);
    NSData* data = (__bridge NSData *)CGDataProviderCopyData(provider);
//    [data autorelease];
    const uint8_t* bytes = [data bytes];
    printf("Pixel Data:\n");
    for(size_t row = 0; row < height; row++)
    {
        for(size_t col = 0; col < width; col++)
        {
            const uint8_t* pixel =
                &bytes[row * bpr + col * bytes_per_pixel];
            printf("(");
            for(size_t x = 0; x < bytes_per_pixel; x++)
            {
                printf("%.2d", pixel[x]);
                if( x < bytes_per_pixel - 1 )
                    printf(",");
            }
            printf(")");
            if( col < width - 1 )
                printf(", ");
        }
        printf("\n");
    }
}

对有问题的HEIC图片,剖析Bitmap值能够发现,关于以RGBA办法排列的Bitmap,白色通明应该为(0,0,0,0),sips东西将PNG转HEIC的编码,在iOS12、13、14上是(71,112,77,112),在iOS15和iOS16上是(71,112,77,00),明显看出sips东西转的HEIC图片之所以在iOS15和iOS16体系上保持无色通明,首要是alpha通道值为0,可是它的实践色彩是绿色。通过试验得知,iOS12、13、14体系的[UIImage imageNamed:]对有Alpha的图片解析Bitmap会稍有差错,导致iOS12、13、14体系上Alpha值不为0,因此显现出绿色。

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

第二步:确认转码东西和紧缩东西对图片的影响

第一步基本能够确认是图片自身的问题,百度APP的图片都是通过紧缩东西紧缩后才集成到APP中的,或许是原PNG图片被处理过导致上述问题。用支撑硬编的iOS12体系的测验机(iPhone7p iOS12),调用Image I/O供给的函数CGImageSourceCreateImageAtIndex先解码PNG图片,然后调用CGImageDestinationCreateWithData 从头编码为HEIC图片。转化的代码:

/// 从支撑解码的图片创立CGImageRef
/// - Parameter path: 图片途径
CGImageRef createCGImageFromFile (NSString* path)
{
    // Get the URL for the pathname passed to the function.
    NSURL *url = [NSURL fileURLWithPath:path];
    CGImageRef        myImage = NULL;
    CGImageSourceRef  myImageSource;
    CFDictionaryRef   myOptions = NULL;
    CFStringRef       myKeys[2];
    CFTypeRef         myValues[2];
    // Set up options if you want them. The options here are for
    // caching the image in a decoded form and for using floating-point
    // values if the image format supports them.
    myKeys[0] = kCGImageSourceShouldCache;
    myValues[0] = (CFTypeRef)kCFBooleanTrue;
    myKeys[1] = kCGImageSourceShouldAllowFloat;
    myValues[1] = (CFTypeRef)kCFBooleanTrue;
    // Create the dictionary
    myOptions = CFDictionaryCreate(NULL, (const void **) myKeys,
                   (const void **) myValues, 2,
                   &kCFTypeDictionaryKeyCallBacks,
                   & kCFTypeDictionaryValueCallBacks);
    // Create an image source from the URL.
    myImageSource = CGImageSourceCreateWithURL((CFURLRef)url, myOptions);
    CFRelease(myOptions);
    // Make sure the image source exists before continuing
    if (myImageSource == NULL){
        fprintf(stderr, "Image source is NULL.");
        return  NULL;
    }
    // Create an image from the first item in the image source.
    myImage = CGImageSourceCreateImageAtIndex(myImageSource, 0, NULL);
    CFRelease(myImageSource);
    // Make sure the image exists before continuing
    if (myImage == NULL){
         fprintf(stderr, "Image not created from image source.");
         return NULL;
    }
    return myImage;
}

/// 将恣意一种格局的图片由UIImage编码为HEIC图片存储
/// - Parameters:
///   - image: <#image description#>
///   - path: <#path description#>
- (void)generateNewHEIC:(UIImage *)image savePath:(NSString *)path{
    NSMutableData *imageData = [NSMutableData data];
    // HEIC图片编码格局
    CFStringRef imageUTType =  CFSTR("public.heic");
    CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)imageData, imageUTType, 1, NULL);
    if (!destination) {
        // 无法编码,基本上是由于目标格局不支撑
        NSLog(@"无法编码");
        return;
    }
    CGImageRef imageRef = image.CGImage; // 待编码的CGImage
    // 可选元信息,比方EXIF方向
    CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationDown;
    NSMutableDictionary *frameProperties = [NSMutableDictionary dictionary];
//    imageProperties[(__bridge_transfer NSString *) kCGImagePropertyExifDictionary] = @(exifOrientation);
    // 添加图画和元信息
    CGImageDestinationAddImage(destination, imageRef, (__bridge CFDictionaryRef)frameProperties);
    if (CGImageDestinationFinalize(destination) == NO) {
        // 编码失利
        imageData = nil;
    }
    // 编码成功,整理……
    CFRelease(destination);
    // 保存新生成的HEIC图片
    if(imageData) {
        NSURL *url = [NSURL fileURLWithPath:path];
        [imageData writeToURL:url atomically:YES];
    }
}

iPhone7p运用ImageI/O将PNG转HEIC的编码则为(00,00,01,01),对测验机自己转化生成的HEIC图片,调用[UIImage imageNamed:]获取UIImage目标,显现出的图片alpha通道没有绿边问题。通过以上操作,基本定位到带Alpha通道图片绿边问题是发生在sips将PNG图片转HEIC图片这一过程中,而且由于这个问题只发生在部分PNG图片上,因此能够得出定论:是原PNG图片被其他紧缩算法处理过,导致sips转HEIC图片发生问题。终究通过排查得出:被pngquant有损紧缩过的带有Alpha通道的PNG图片,无法正确的转为HEIC图片。

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

五、图片最佳实践

iOS包体积首要由代码和资源组成,在包体积优化实践中发现,相较于代码,资源收益更简单落地。百度APP内常用的的资源优化办法有:PMS下发、ZIP紧缩、图片紧缩和格局转化等。为了避免后续新增大资源和大图片问题,在RD提交代码时,优化git hook功用:

1、修正阻拦阈值,大资源和大图片准入阻拦阈值从50KB下降至20KB;

2、添加图片优化提示功用,提交新图片时主动对图片进行紧缩和格局转化,给出图片最佳巨细主张。

5.1 方案

1、在履行git commit时,检测提交的文件,假如为非代码文件,检测文件巨细。大资源和大图片准入阻拦阈值从50KB下降至20KB;

2、核算各种优化后的图图片在Bundle和Asset Catalog里的巨细,核算出最佳的优化办法,文字提示RD优化,不会阻拦提交

3、图片优化办法分为两类,一类是寄存位置,放Bundle和放Asset Catalog;另一类是对图片进行处理,有紧缩和转化格局两种处理办法,两种相互结合得到最佳办法:

  • 放Bundle:不引荐,图片在装置包内体积为图片自身巨细,Xcode不会处理,在iOS11以下体系中无法兼容HEIC图片;

  • 放Asset Catalog:引荐,Xcode打包编译时会用actool东西处理图片,优化图片巨细,能够兼容HEIC图片;

  • pngquant紧缩:有损紧缩PNG图片,沿袭百度APP之前的图片紧缩参数,是git hook原有逻辑;

  • MozJPEG紧缩:有损紧缩JPG图片,新增的紧缩东西,calendar.perfplanet.com/2014/mozjpe…

  • HEIC图片:无损转化,百度APP中只能在Asset Catalog中运用,需求回归iOS11体系是否正常显现;

需求留意: 由于图片放置在Asset Catalog中,Xcode打包编译时会用actool东西处理图片,所以装置包内的图片巨细并不等于图片自身巨细。脚本会将图片编译生成Asset.car文件,读取在图片在装置包内的实践巨细。

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△图片提交检测

六、检测无用类

6.1 无用类检测原理剖析

百度APP是一个十分大的工程,每个版别都会新上很多需求,可是跟着人员的变动、运营活动和版别迭代,有些功用现已没有入口,有些代码现已不会被引证到,比方现已固化了AB试验代码,云控开关代码等,代码重构过程中也会有冗余的代码忘记删去,活动下线后代码仍旧遗留在工程中等。从代码视点优化包体积,无用代码检测是一种不错的解决方案,代码优化包括无用类、无用办法、重复代码、运营代码等纬度,下文要点介绍无用类的检测和优化。无用类检测的难点在于OC是一门动态言语,在检测时误报的概率会很大,会给RD形成人力糟蹋。无用类检测总体思路分为两部分,静态检测和动态检测相结合。

静态检测是从编译产品的视点剖析代码引证联系和结构。剖析Linkmap文件和Mach-O文件,依据Segment里的数据,查找出没用被引证的Class和method。可是这种办法具有很大的局限性,比方某些能受运营影响,如何履行是Server端云控决定的;还有些通过Runtime进行初试化的Class也无法被辨认。

动态剖析是从代码运转的视点剖析代码是否被初始化,在APP运转期间,记载APP生命周期中初始化过的Class,用户运用到的功用所触及的相关Class都会被记载到,反之某些功用没有被运用,那这些对应的类则不会被记载。动态检测是穷举APP一切功用在运用过程中运用过的Class。

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△无用类剖析与分发

七、静态剖析

静态剖析需求用到Linkmap文件和Mach-O文件,Linkmap文件记载着一切Symbol address、Symbol、Symbol Size的对应联系,Mach-O文件记载着类结构和地址。结合Linkmap文件和Mach-O能够复原出每个Class的一切信息。

Mach-o文件中__DATA __objc_classrefs段记载了引证类的地址,__DATA __objc_classlist段记载了一切类的地址,取差集能够得到未运用的类的地址,然后进行符号化,就能够得到未被引证的类信息。

7.1 解析Mach-O

能够通过Mac自带的东西otool打印Mach-o中的段信息。

% file -b BaiduBoxApp.app/BaiduBoxApp #获取Mach-O架构
Mach-O 64-bit executable arm64

% otool -arch arm64 -oV BaiduBoxApp.app/BaiduBoxApp > ovrelease.txt #解析Mach-O内容

输出的内容首要包括以下几个部分,其中__DATA,__objc_classlist便是类的全集,__DATA,__objc_classrefs是被引证到的类。假如是Debug包,能够直接获取类名,可是release包一般只要符号地址,利用这些地址能够在对应和Linkmap文件中复原出符号,也便是能够得到详细的类名。

'Contents of (__DATA,__objc_classlist) section',  # classlist节标识
'Contents of (__DATA,__objc_classrefs) section',  # classrefs节标
'Contents of (__DATA,__objc_superrefs) section',  # 父类节标
'Contents of (__DATA,__objc_catlist) section',  # category节标
'Contents of (__DATA,__objc_protolist) section',
'Contents of (__DATA,__objc_selrefs) section',
'Contents of (__DATA,__objc_imageinfo) section'

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△debug包

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△release包

7.2 留意点

1、在实践剖析的过程中发现,假如一个类的子类被实例化,父类未被实例化,此刻父类不会呈现在__objc_classrefs这个段里,在未运用的类中需求将这一部分父类过滤出去。

2、多个类中或许存在相同的办法名。由于MachO文件中__cstring和__objc_methname这两个代码段记载的是办法名字符的ASCII码的十六进制表示。假如多个类中有相同的办法名,相同的办法名会进入link map的Dead Stripped Symbols中,最后只留一个。

3、假如做了段搬迁,或许导致otool东西无法解析对应办法名,可是通过符号地址咱们能够在linkmap里复原出详细符号。

百度APP iOS端包体积50M优化实践(五) HEIC图片和无用类优化实践

△Symbol解析

八、动态剖析

8.1 动态剖析原理

OC的类结构体中,都存在一个isa指针,指向对应类的meta-class。咱们通过对meta-class的结构体的剖析,能够发现在meta-class的class_rw_t中,有一个flag标志位,通过flag标志位核算,能够获悉当时类在运转时中是否被初始化过。

// class is initialized
#define RW_INITIALIZED        (1<<29)
struct objc_class : objc_object {
    bool isInitialized() {
    return getMeta()->data()->flags & RW_INITIALIZED;
    }
};

以上摘取objc-runtime源码中,objc_class结构下获取当时类是否已被初始化的函数。但在使用中,咱们无法直接调用类结构体中的函数,所以在百度APP工程中,自定义与体系类相同的结构体,并完成相应isInitialized()函数。通过赋值转化,咱们能够拿到指定类对应meta-class中的数据,即能够判断指定类是否在当时生命周期中是否被初始化过(被运用过)。

8.2技术完成

#define RW_INITIALIZED        (1<<29)
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
# endif
struct lazyFake_objc_class : lazyFake_objc_object {
    //供给metaClass函数,获取元类目标
    lazyFake_objc_class* metaClass() {
        #if __LP64__
        //isa指针需求通过一次 &ISA_MASK操作之后才得到真正的地址
            return (lazyFake_objc_class *)((long long)isa & ISA_MASK);
        #else
            return (lazyFake_objc_class *)((long long)isa);
        #endif
    }
    bool isInitialized() {
        return metaClass()->data()->flags & RW_INITIALIZED;
    }
};

2. 首要获取百度APP工程中一切自定义OC类

Dl_info info;
dladdr(&_mh_execute_header, &info);
classes = objc_copyClassNamesForImage(info.dli_fname, &classCount);

3. 遍历自定义类,并逐个对其进行赋值转化为自定义结构体,并通过自定义类结构办法,获取当时类是否被初始化过。

struct lazyFake_objc_class *objectClass = (__bridge struct lazyFake_objc_class *)cls;
BOOL isInitial = objectClass->isInitialized();

九、总结

1、HEIC图片相较于PNG,对部分图片能够下降图片体积,收益从10%-70%不等,详细问题详细剖析,编写git hook查看脚本供给指导;

2、HEIC图片放Asset Catalog能够兼容iOS10以上的一切机型和体系;

3、HEIC图片放Bundle只能在iOS12体系上解码,这个和Apple给出的定论相悖。若APP最低支撑体系小于iOS12,则HEIC图片制止放Bundle。A9以上芯片的机型为硬解,速度更快;

4、带有Alpha通道的PNG图片,未通过pngquant有损紧缩的,利用sips指令直接转HEIC图片能够正常显现;

5、带有Alpha通道的PNG图片,现已被pngquant有损紧缩过的在iOS12,13,14体系上会显现绿幕,iOS115,iOS16显现正常。尽管显现正常,可是RGB位图色彩解码过错,仅仅由于alpha为0,绿色变成了通明;

6、无论是PNG仍是HEIC图片,在Asset Catalog办理下,打包生成的体积和原图片不同,都会通过不同的处理紧缩,或许变大也或许变小,以终究产品为准;

7、pngquant合适对Bundle里的PNG紧缩,获取收益,对Asset Catalog里的图片不应该处理,由于这个收益其实是有损紧缩获取的,并且会导致紧缩过的带Alpha通道的PNG无法转HEIC;

8、无用类检测结合动态检测和静态检测,检测较为严格,首要是为了下降误报率,下降对RD的影响,实践操作过程中发现有些无用类会被漏检。准确度和掩盖度需求依据需求动态调整。

—— END——

参考文献

[1]、503_WWDC 2017 CMF_03_D:

devstreaming-cdn.apple.com/videos/wwdc…

[2]、iOS代码瘦身实践:删去无用的类:

httpshttps:///post/6844903922201526285

引荐阅览

百度知道上云与架构演进

百度APP iOS端包体积50M优化实践(四)代码优化

百度App启动性能优化实践篇

扫光动效在移动端使用实践

Android SDK安全加固问题与剖析

查找语义模型的大规模量化实践