iOS App瘦身记录

问题:

App Store规定安装包大小超过150MB的App只能在WIFI环境下载。现在项目App包已经超过这条线,这意味着可能将损失大量用户,需要对其进行瘦身

App现状

archive后,ipa包大小为132.3MB,.app大小为166MB,Mach-O大小为86.1MB,Assets.car大小为61.4MB

ipa包:archive后生成的文件,实际为一种压缩包,解压后包含.app文件和symbols信息。根据app store规定,.app文件超过150MB则无法进行OTA升级
app文件:实际的应用程序包,里面包含app同名的Mach-O可执行文件,Asset.car、.nib、.bundle、Localizable.strings等资源文件,_CodeSignature文件夹包含签名信息。
Mach-O文件:代码编译链接后生成的可执行文件,包含支持的所有cpu指令集。可以通过Link-Map分析其中所有方法和类所占空间的大小。
Assets.car:xcassets图片文件。
因此,对App瘦身实际上就是减小.app文件的大小

查看.app包内容规划此次App瘦身的目标:针对大于100Kb的文件进行瘦身,包含以下两点:
1.整理资源文件,包括文件压缩与无用资源的删除
2.整理可执行文件,包括删除无用类和无用函数

.app文件包内容

瘦身方法

1.LSUnusedResources查找并删除无用文件
先勾选"Ignore similar name"过滤掉以"tag_%d"命名的文件后再去掉勾选,多筛几遍
瘦身效果:129.1MB;app文件大小:160.8MB; X1可执行文件:86.1MB;Assets.car:56.2MB

2.Link-Map分析并删除无用三方库
使用Link-Map分析工具得出每个类或者库所占用的空间大小,可以快速定位需要优化的类或静态库。
瘦身效果:123.7MB;app文件大小:156.8MB; X1可执行文件:84.1MB;Assets.car:56.2MB

3.用LaunchScreen.storyboard替代启动图
上图.app包中PNG图片占用大量空间,全部为LaunchImage中的全尺寸启动图。删除后用storyboard替代,注意为UI控件添加合适约束以适应不同尺寸的手机。
瘦身效果:ipa包大小:116.1MB;app文件大小:147.7MB; X1可执行文件:84.1MB;Assets.car:56.2MB

4.选择合适的cpu指令集进行编译
使用MachOView查看app中的Mach-O文件,armv7占了一小半

mach-o文件体积

9012年如果追求app体积最优解,可以去掉对armv7的支持(iphone4,iphone4s),当然指令集是向下兼容的,如果想全尺寸支持可以选择armv7(iphone5,iphone5c向下兼容armv7)和arm64。这里我选择去掉armv7。
瘦身效果:ipa包大小:80.4MB;app文件大小:107.5MB; X1可执行文件:45.9MB;Assets.car:56.2MB

5.python脚本查找并删除无用类
分析了两天破解版和未破解版的AppCode,得出的unused code只有import问题,后来参考这里,一个简单的脚本分析出无用的类。

# -*- coding: UTF-8 -*-
#!/usr/bin/env python
# 使用方法:python py文件 Xcode工程文件目录

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

删除项目中无用类。
瘦身效果:ipa包大小:80.1MB;app文件大小:107.2MB; X1可执行文件:45.6MB;Assets.car:56.2MB

6.xcassets图片压缩
项目使用xcassets管理图片
1)使用ImageOptime工具进行后,编译时xcode会还原压缩后的png图片,需要设置COMPRESS_PNG_FILESSTRIP_PNG_TEXTNo
2)TinyPng对大尺寸PNG图片进行压缩,免费版每日有压缩数量限制。项目压缩130Kb以上png图片,再替换xcassets中原图。
瘦身效果:ipa包大小:69.5MB;app文件大小:96.4MB; X1可执行文件:45.6MB;Assets.car:45.3MB

小结

总结就是资源和代码两方面下手,xcode编译设置其实作用感觉不太大,其实最关键的还是需要结合自身项目,分析其构成,来找到适合自己项目的瘦身方法。

参考

ipa包大小优化

iPhone安装包的优化

iOS APP安装包瘦身实践

包大小:如何从资源和代码层面实现全方位瘦身?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271