iOS瘦身实践

资源级的瘦身

  • 使用LSUnusedResources删除无用图片。注意只是简单的删除。需要自己在此确认。
  • 使用WebP代替PNG, 转换及压缩工具isparta
  • WebP的优点:
    1. Webp 压缩率⾼,⽀持有损与⽆损压缩
    2. WebP 体积⼤幅减少,⾁眼看不出差异
      缺点:Webp更加消耗性能,较PNG消耗2倍左右的CPU和解码时间

代码级的瘦身-linkmap

首先需要了解linkmap是什么?LinkMap文件是Xcode产生可执行文件的同时生成的链接信息,用来描述可执行文件的构造成分,包括代码段(__TEXT)和数据段(__DATA)的分布情况。比如说可执行文件的构成是怎样,里面的内容都是些什么,

  • 默认xcode在debug下是不生成linkmap文件的,所以我们首先要设置在debug模式下开启生成linkmap,在build setting中搜索link map,设置write link map file为YES,再次运行可以可以在 path to link map file中找到对应生成的text文件。以为自己的地址举例:/Users/用户名/Library/Developer/Xcode/DerivedData/项目名称-hhqqxhkyzdufzbfyuefaytyuxebf/Build/Intermediates.noindex/项目名称.build/Debug-iphoneos/项目名称.build/项目名称-LinkMap-normal-arm64.txt其中项目名称后面的随机字符串可能不一致。

  • 分析生成的link map中的执行文件大小。WMLinkMapAnalyzer。可以查看静态库是否过大。如果过大可以参考lipo -info libWeChatSDK.a Architectures in the fat file: libWeChatSDK.a are: armv7 armv7s i386 x86_64 arm64
    i386,x86_64,是模拟器的指令集,如果不考虑模拟器运行,可以删除。

使用下面脚本或者使用appcode的code inspect 来检查未使用的类和方法
// 查找无用的类脚本

# 使用方法:python py文件 Xcode工程文件目录

# -*- coding:UTF-8 -*-
import sys
import os
import re

if len(sys.argv) == 1:
    print '请在.py文件后面输入工程路径' 
    sys.exit()

projectPath = sys.argv[1]
print '工程路径为%s' % projectPath

resourcefile = []
totalClass = set([])
unusedFile = []
pbxprojFile = []

def Getallfile(rootDir): 
    for lists in os.listdir(rootDir): 
        path = os.path.join(rootDir, lists) 
        if os.path.isdir(path): 
            Getallfile(path) 
        else:
            ex = os.path.splitext(path)[1]  
            if ex == '.m' or ex == '.mm' or ex == '.h':
                resourcefile.append(path)
            elif ex == '.pbxproj':
                pbxprojFile.append(path)

Getallfile(projectPath)

print '工程中所使用的类列表为:'
for ff in resourcefile:
    print ff

for e in pbxprojFile:
    f = open(e, 'r')
    content = f.read()
    array = re.findall(r'\s+([\w,\+]+\.[h,m]{1,2})\s+',content)
    see = set(array)
    totalClass = totalClass|see
    f.close()

print '工程中所引用的.h与.m及.mm文件'
for x in totalClass:
    print x
print '--------------------------'

for x in resourcefile:
    ex = os.path.splitext(x)[1]
    if ex == '.h': #.h头文件可以不用检查
        continue
    fileName = os.path.split(x)[1]
    print fileName
    if fileName not in totalClass:
        unusedFile.append(x)

for x in unusedFile:
    resourcefile.remove(x)

print '未引用到工程的文件列表为:'

writeFile = []
for unImport in unusedFile:
    ss = '未引用到工程的文件:%s\n' % unImport
    writeFile.append(ss)
    print unImport

unusedFile = []

allClassDic = {}

for x in resourcefile:
    f = open(x,'r')
    content = f.read()
    array = re.findall(r'@interface\s+([\w,\+]+)\s+:',content)
    for xx in array:
        allClassDic[xx] = x
    f.close()

print '所有类及其路径:'
for x in allClassDic.keys():
    print x,':',allClassDic[x]

def checkClass(path,className):
    f = open(path,'r')
    content = f.read()
    if os.path.splitext(path)[1] == '.h':
        match = re.search(r':\s+(%s)\s+' % className,content)
    else:
        match = re.search(r'(%s)\s+\w+' % className,content)
    f.close()
    if match:
        return True

ivanyuan = 0
totalIvanyuan = len(allClassDic.keys())

for key in allClassDic.keys():
    path = allClassDic[key]

    index = resourcefile.index(path)
    count = len(resourcefile)

    used = False

    offset = 1
    ivanyuan += 1
    print '完成',ivanyuan,'共:',totalIvanyuan,'path:%s'%path


    while index+offset < count or index-offset > 0:
        if index+offset < count:
            subPath = resourcefile[index+offset]
            if checkClass(subPath,key):
                used = True
                break
        if index - offset > 0:
            subPath = resourcefile[index-offset]
            if checkClass(subPath,key):
                used = True
                break
        offset += 1

    if not used:
        str = '未使用的类:%s 文件路径:%s\n' %(key,path)
        unusedFile.append(str)
        writeFile.append(str)

for p in unusedFile:
    print '未使用的类:%s' % p

filePath = os.path.split(projectPath)[0]
writePath = '%s/未使用的类.txt' % filePath
f = open(writePath,'w+')
f.writelines(writeFile)
f.close()

以上查找无用类的方法只能作为参考。还需要人工筛选一遍。很多通过xib创建、或者cell通过register方法创建的。脚本是无法区分的。所以还需人工审查一遍。但也已经大大减少了很多繁琐的工作量了。

编译器级别瘦身。

  • 这个最有用的一个选项是Deployment Postprocessing / Strip Linked Product / Symbols Hidden by Default 在release版本应该设为yes

更多的瘦身方法

参考滴滴出行iOS端瘦身实践

个人总结:项目尝试过上述几种方法,Xcode最终打出ipa包由原来的63M缩小到了50M。因为项目原因,个人感受这边最为行之有效的应该算是资源的压缩。可能根据不同项目的场景各有不同。大家都可以进行尝试!

参考:

滴滴出行iOS端瘦身实践

当我们谈论iOS瘦身的时候,我们到底在谈论些什么

猜你喜欢

转载自blog.csdn.net/yuwuchaio/article/details/80067637