精华内容
下载资源
问答
  • iOS应用瘦身

    2019-04-23 15:19:12
    iOS应用瘦身 iOS9新特性,根据用户设备型号,在保证应用程序完整的情况下,尽可能的压缩和减少应用程序安装包的体积,实现方法主要有三种:应用程序切片(Slicing)

    iOS应用瘦身

    iOS9新特性,根据用户设备型号,在保证应用程序完整的情况下,尽可能的压缩和减少应用程序安装包的体积,实现方法主要有三种:应用程序切片(Slicing)?、中间代码(Bitcode)和按需加载资源(On?Demand Resources)

    1、切片是创建和提供不同的目标设备的应用程序包的变体(variant)的过程。一个变体(variant)只包含可执行架构和目标设备所需要的资源,应用程序切片只提供给与每个设备相关的资源(取决于屏幕分辨率和架构等等)。实际上,应用程序切片完成了 App 瘦身的大部分工作;

    2、Bitcode本质上,它是在 App 被下载前,苹果优化它的新途径。中间代码使得 App 可以在任何设备上尽可能快速和高效执行。中间代码可以为最近使用的编译器自动编译 App,并且对特定的架构做优化(例如 arm64 64 位处理器),可以在 Build Setting 下把 Enable bitcode 修改为 YES;

    3、 按需加载的资源就是在 App 初次安装后需要下载的文件,在 Xcode 的设置中(在 Build Setting 里),开启按需加载资源需要把”Enable On Demand Resources”改为”YES”。

    展开全文
  • iOS应用瘦身方法思路整理 一、前言 前段时间注意到我们APP的包大小超过100MB了,所以随口跟吕老板说了下能否采用字体文件(.ttf)替代PNG图片,吕老板对应用瘦身很感兴趣因此让我做下技术调研。这篇文章主要是...

    iOS应用瘦身方法思路整理


    一、前言

    前段时间注意到我们APP的包大小超过100MB了,所以随口跟吕老板说了下能否采用字体文件(.ttf)替代PNG图片,吕老板对应用瘦身很感兴趣因此让我做下技术调研。这篇文章主要是将我们的各个技术方案的思路做一下整理和总结,希望对大家有所帮助。

    二、iOS内置资源的集中方式

    在介绍技术方案前我们先来看下iOS内置图片资源都有哪些常见的方式:

    1、将图片存放在bundle下

    这是一种非常常见的方式,项目中各类文件分类放在各个bundle下,项目既整洁又能达到隔离资源的目的。我们项目中图片绝大多数都是这样内置的,其加载方式为[UIImage imageNamed:”xxx.bundle/xxx.png”](请记住这个字符串的规则,因为这种规则非常非常重要!!!”xxx.bundle/xxx.png”)。但是这种方式有比较明显的缺点:首先使用bundle存储图片iOS系统不会对其进行压缩存储,造成了应用体积的增大。其次是使用bundle存储图片放弃了APP thinning,其明显的表现是使用2倍屏手机的用户和使用3倍屏手机的用户下载的应用包大小一样。如果能够实现APP thinning,那么往往2倍屏幕的手机包大小会小于3倍屏手机的包大小,起到差异性优化的目的。在调研过程中我们还发现,应用的体积与图片资源的数量密切相关(听起来好像是废话)。换句话说,iPhone的rom存在4K对齐的情况,一张498B大小的图片在应用包中也要占据4KB大小。因此项目中每添加一张图片就至少增大了4KB。为了证实这个观点特地创建空应用进行测试。首先创建空应用,其大小在7P上为213KB,引入一张498B的图片前后对比如下:

    一张498B的图片 占据4KB磁盘空间
    这里写图片描述

    这里写图片描述

    未添加资源的应用

    这里写图片描述

    添加图片资源后的大小
    上述实验未经过App Store上线认证,仅仅通过本地打包测试,因此观点仅供参考。

    2、使用.ttf字体文件替代图标

    使用字体文件替代图片也是一种比较常见的资源内置方式。很多应用都使用过这种方案,如淘宝、爱奇艺等知名应用,都采用过这种方式。使用字体文件的好处是显而易见的,如果APP中某个图片比较大,那么为了保证清晰度,UI可能会提供比较大的图标。使用字体文件会避免这个问题,而且不必导入@2x和@3x图片,一套字体文件就能保证UI的清晰度。关于如何生成.ttf文件在这里就不在赘述了(因为我并不喜欢这个方案),我们只要如何使用就可以了。字体文件使用起来比较简单,但是使用方法与png图片的使用方法有很大的不同,因为字体文件时机所展示的图标都是UTF8编码转来的字符串。因此当我们需要展示一个图标的时候不再是使用UIImageView了,而是UILabel。

    这里写图片描述

    字体文件展示图片的代码示例
    由于我们使用了字体来替代图片,所以我们可以通过设置字体的颜色来改变图标的颜色。我们之前经常会遇到一个场景,如两个一模一样的图标但是由于颜色不同,UI同学就需要提供2套图片,每套图片中包含@2x和@3x图片。如果采用了字体替代简单的图标,那么UI只需要提供一套字体即可,并且拉伸后也不会失真。使用字体文件的好处总结起来主要有两点:

    1、可以降低应用图片内置资源的体积。

    2、可以随意放缩和修改颜色。

    但是其缺点也很明显:

    1、图标的查找和替换比较麻烦,不如直接使用图片那样简单。

    2、最重要的是如果在58同城APP中使用,则意味着无法替换之前存在的图片,只能起到缩小增量的目的,无法减小全量。

    ps:任何一种需要大刀阔斧改革的优化都是一种不明智的行为。

    3、图片存在Assets.xcassets下(苹果推荐,我也推荐)

    使用Assets.xcassets是苹果推荐的一种方式。Assets.xcassets是iOS7推出的一种图片资源管理工具,将图片内置到Assets.xcassets下系统会对图片资源进行压缩,并且支持APP thinning。

    这里写图片描述

    APP Slicing
    项目优化不能脱离场景,很多很好的方案由于场景的限制并不能起到优化的作用。因此先简单介绍下我们的项目场景:为了达到跨团队快速开发的目的,我们项目很早就利用cocoapods实现组件化。项目中存在多个业务pod,每个pod都有各自的团队维护,各个团队的代码彼此不开放,各个pod最终会被编译为.a的形式。这里需要说明一下我为什么要强调.a,与.a相对应的还有一个.framework,他们之间有一个重要的区别就是资源的问题。framework中可以存放资源,但是.a却不可以,因此生成.a的pod下的资源会被转移到main bundle下,这为资源冲突造成了隐患,为了避免这种冲突我们之前采用的使用bundle管理资源,bundle名很少会重复这样就大大降低了资源冲突的可能性。优化的前提之一也是不破坏这种组件化开发的模式,换句话说也就是各个业务线不产生资源耦合、业务线的RD不必担心彼此资源的冲突、业务Pod下的资源文件彼此隔离。哪怕招聘团队中存在a.png,房产团队中也存在a.png也不会有什么问题。所以我们先要抛出两个问题:

    1、cocoapods是否支持使用Assets.xcassets。

    2、各个pod各自维护自己的Assets.xcassets会不会造成资源冲突。

    为了弄清楚上面两个问题,我们先要看下podspec的几个重要参数:

    这里写图片描述

    podspec
    s.public_header_files :表明了哪些路径下的文件可以在framework外被引用。

    source_files :源文件路径。

    s.resources :资源文件路径及文件类型。

    s.resource_bundles :资源文件路径及类型,同时资源文件会被打成bundle。(推荐使用)。

    实验发现各个pod下都可以创建自己的xcassets,因此问题1不算问题是问题。如果我们在各个业务pod下都创建.xcassets文件内置图片,那么cocoapods的脚本会在编译时将各个目录下的xcassets文件内容提取出来,合并到一个xcassets中并生成一个.car文件。这样的话如果资源文件重名,那么很可能其中某一个文件会被覆盖替换。因此我们主要是要解决问题2。查看podspec的写法发现s.resource_bundles貌似是我们所需要的法宝。为此我们天真的以为问题马上就要解决了:

    这里写图片描述

    将指定路径下的资源打包成bundle
    最终打包结果很理想,确实能够生成ImagesBundle.bundle,并且bundle下存在Assets.car。

    这里写图片描述

    mainbundle下存在ImagesBundle

    这里写图片描述

    ImageBundle.bundle下存在Assets.car
    事情到这里可能已经看到曙光了,但是我们发现通过

    [UIImage imageNamed:@”ImagesBundle.bundle/1”];

    加载不出来图片。必须使用

    [UIImageimageNamed:@”1”inBundle:[WBIMViewControllericonBundle]compatibleWithTraitCollection:nil];

    才能加载出来。

    这里写图片描述

    图片加载失败

    这里写图片描述

    指定bundle后加载成功
    也就是说只有Assets.car如果不在main bundle下,那么加载图片都需要指定bundle。

    既然需要指定bundle加载图片,那么如何获取这个bundle呢?换句话说如何才能低成本的将现在项目中的图片放到特定bundle下的Assets.car文件中呢?对此我们提出了一个解决方案:

    1. 在pod下新建一个空文件夹。找出该pod存放图片的所有bundle,在新建文件夹下创建与bundle数量相等的Asset。

    2. 修改podspec文件,设置resource_bundles将Asset指定为资源,并指定bundle名称。如A.bundle,其对应的Asset最终资源bundle为A_Asset.bundle。

    3. 新增方法,imageWithName:,从符合xxx.bundle/yyy.png特征的参数中获取bundle名和图片名xxx_Asset.bundle和yyy.png,获取图片并返回。

    4. 查找并全部替换imageNamed: 和 imageWithContentOfFile:为imageWithName:

    只要能拿到原来代码中imageNamed:的参数就能知道现在图片存在那个bundle下,这样就能通过imageNamed:inBundle:获取到图片,其思路如下图所示:

    这里写图片描述
    imageWithName:方法内部处理

    打包后bundle情况
    看到这里老司机们已经应该能遇见这种优化的成本了。加载图片都需要指定bundle也就意味着成千上万处的API需要修改。我们最初探讨到这里的时候首先想到的是脚本,但是这个方案很快就被否定了,因为项目中存在大量的XIB,XIB中设置图片我们无法通过脚本替换API。

    为了解决XIB设置图片的问题,我们首先想到了AOP。通过hook XIb加载图片的方法将方法偷偷替换为imageNamed:inBundle: ,但是很遗憾我们hook了UIImage所有加载图片的方法,没有一个方法能拿到XIB上所设置的图片名称,也就意味着我们无法得知优化后的图片在哪个bundle下,也就不知道图片该如何加载。虽然有坎坷,但是我们始终坚信XIB一定是通过某些方法将图片加载出来的,我们一定能拿到这个过程!为了验证这个问题,首先定义一个UIImageView 的子类,并将XIB上的UIImageView指定为这个子类。大家都知道通过XIB加载的视图都一定会执行initWithCoder:方法

    这里写图片描述

    UIImageView的子类加载
    我们发现在得到执行[super initWithCoder:aDecoder]之前通过lldb查看slef.image是nil。当执行完这行代码后self.image就有值了。因此推断图片的信息(图片名称、路径等信息)都在aDecoder中!在网上搜索了一些资料后发现aDecoder有一些固定的key,可以通过这些固定的key得到一部分信息。如

    这里写图片描述

    aDecoder可以通过某些key得到其中信息
    很显然通过“UIImage”这个key能拿到图片,但是很遗憾经过多次尝试没能找到图片的路径信息。因此这个问题的关键是怎么找到合适的key,为了解决这个问题,最好是能拿到aDecoder的解码过程。因此hook aDecoder的解码方法decodeObjectForKey:是个不错的选择。如果能拿到xib上设置的图片名称那么我们就可以根据图片名称获取到正确的图片路径。经过断点查看aDecoder 是UINibDecoder(私有类)类型。
    这里写图片描述

    aDecoder
    这里写图片描述

    hook UINibDecoder的decode方法

    打印系统decode的所有key 后发现有个key为UIResourceName,value为图片的名称。也就是说我们能得到XIB上设置的图片名称了。但是这个图片的名称怎么传递给这个XIB对应的UIImageView 对象呢?换句话说也就是说我们怎么把图片传给这个XIB对应的view呢?为了将图片名称传给UIImageView,需要给aDecoder添加一个block的关联引用。
    这里写图片描述

    UIImageView在initWithCoder:的时候设置回调
    在hook到的decodeObjectForKey:方法中将图片名称回传给initWithDecoder:方法:
    这里写图片描述

    aDecoder hook到图片名称后回调给UIImageView类
    这里需要注意的是一点是:XIB 默认设置图片是在rentun value之后,也就是说如果我们回调过早有可能图片被替换为nil。因此需要dispatch_after一下,等return 之后再回调图片名称并设置图片。受此启发,我们也可以hook UIImage 的imageNamed:方法,根据参数的规则到xxxCopy.bundle下获取图片,并返回图片。这就意味着放弃通过脚本修改API,减少了代码的改动。看到这里似乎是没有什么问题,但是我们忽略了一个很严重的问题aDecoder对象和UIImageView类型的对象是一一对应的吗?一个imageView它的aDecoder是它唯一拥有的吗?带着这个问题,我们先来看下打印信息:

    这里写图片描述

    重复生成UIImageView对象和aDecoder对照关系
    重复生成对象并打印后发现aDecoder的地址都相同,也就是说存在一个aDecoder对应多个UIImageView的现象。因此异步方案不适用,需要同步进行设置图片,因此全局变量最为合适。其实这一点很容易理解,aDecoder是与XIB对应的,XIB是不变的所以aDecoder是不变的。因此异步回调的方案不适用,需要同步进行设置图片,在这种情况(主线程串行执行)下跨类传值全局变量最为合适:

    这里写图片描述

    hook UINibDecoder的decodeObjectForKey
    这里写图片描述

    hook UIImageView 的initWithCoder:
    上面两段代码仅仅介绍思路,可能加载图片的代码并不是十分的严谨,请读者自己鉴别。同理hook 项目中UIImage 所用到的加载图片的API即可加载图片。如果将所有的hook方法放到一个类中,那么只要将这个类拖入到项目中,并将项目中所有的bundle下的图片都放到对应的Assets.xcassets文件下那么无需修改一行代码即可将所有的图片迁移到Assets.xcassets下,达到应用瘦身的目的。但是我们组内老练的架构师们指出:项目中hook如此重要的API对增加了项目维护的难度。这也引发了我对项目中AOP场景的思考,项目中到底hook 了多少API?可能在我场多年的老司机们都难以回答了,为此特地赶制了一个基于fishhook的一个hook打印工具,检测和统计项目中的AOP情况。但是缺点是必须调整编译顺序保证工具类最先被load。

    这里写图片描述

    hook method_exchangeImplementations 方法

    这里写图片描述

    展开全文
  • iOS应用瘦身总结

    2019-05-25 06:44:35
    1、LSUnusedResources去除冗余图片 通过以下地址下载LSUnusedResources项目。运行项目,选择需要扫描的项目路径,找出项目当中的冗余图片资源。这里需要注意的是,在扫描前需要勾选Ignore similar name选项。...

    1、LSUnusedResources去除冗余图片

    通过以下地址下载LSUnusedResources项目。运行项目,选择需要扫描的项目路径,找出项目当中的冗余图片资源。这里需要注意的是,在扫描前需要勾选Ignore similar name选项。

     

     

     

    2、ImageOptim图片无损压缩

    使用ImageOptim工具对图片进行无损压缩

     

     

     

    3、项目中较大的资源例如音频、视频等,建议在项目安装登录之后下载到本地。

    4、SameCoderFinder去除相同代码

    通过以下地址下载SameCodeFinder脚本

    通过命令检测本地是否安装python

    查看python 2的版本

    python --version
    复制代码

    查看python 3的版本

    python3 --version
    复制代码

    查看 pip 版本和位置(视系统和 Python 版本的不同命令可能为 pip 或 pip3)

    pip --version
    复制代码

    如果没有安装python和pip,可以通过以下命令来安装python

    安装python3

    brew install python3
    复制代码

    下载安装脚本

    curl https://bootstrap.pypa.io/get-pip.py
    复制代码

    安装pip

    python3 get-pip.py
    复制代码

    安装结束之后通以下方式安装simhash

    pip install simhash
    复制代码

    最后cd到SameCodeFinder.py存放的目录,执行以下命令

    python SameCodeFinder.py ~/Projects/opensource/MWPhotoBrowser/ .m  --max-distance=10 --min-linecount=3 --functions --detail
    复制代码

    注意:其中~/Projects/opensource/MWPhotoBrowser/需要替换为本地项目路径。分析的结果会在SameCodeFinder.py所在路径下输出为out.txt文件。最后一位数字代表两个文件的海明距离,数字越小说明两个文件越类似,如下:

     

     

     

    5、AppCode

    使用AppCode检测出项目中未使用的类和方法,下载地址:AppCode,密码:d87i

     

    展开全文
  • 前言最近组里的项目遇到了一个瓶颈问题:代码段超标,简单的说,就是编译后输出的可执行文件太大了,来看看 官方文档 中的相关规定:For iOS and tvOS apps, check that your app size fits within the App Store ...

    前言

    最近组里的项目遇到了一个瓶颈问题:代码段超标,简单的说,就是编译后输出的可执行文件太大了,来看看 官方文档 中的相关规定:

    For iOS and tvOS apps, check that your app size fits within the App Store requirements.
    Your app’s total uncompressed size must be less than 4GB. Each Mach-O executable file (for example, app_name.app/app_name) must not exceed these limits:

    For apps whose MinimumOSVersion is less than 7.0: maximum of 80 MB for the total of all TEXT sections in the binary.
    For apps whose MinimumOSVersion is 7.x through 8.x: maximum of 60 MB per slice for the TEXT section of each architecture slice in the binary.
    For apps whose MinimumOSVersion is 9.0 or greater: maximum of 500 MB for the total of all __TEXT sections in the binary.

    可以看到,iOS 9+ 支持 500MB 的代码段体积,而 iOS 8.x 只支持 60MB。面对不断增加的业务代码,我们需要一个手段,来及时删除已经废弃的代码,以减小代码段体积。

    在尝试分析 LinkMap 文件无果之后,我找到了另外一个路线,那就是分析 Clang AST,在静态分析时从语法树中,找到未被显示调用到的方法。尽管由于 oc 的动态特性,即便静态阶段其未被显示调用,它依然可能在动态期间被调用,但不论如何,我们都可以通过分析 AST 来得到未被静态调用的方法,对它们进行校对、确认。

    Clang & LLVM

    有关 Clang 和 LLVM 的知识,远不是三言两语能讲完的,我个人对这块也不是十分熟悉,感兴趣的推荐 一篇非常深入的文章,油管上也有一个简明扼要的 介绍LLVM的视频 可以用于入门。当然,遇事 Google 一下总能得到许多有用的结果。

    简单的说,Clang 是 LLVM 编译器前端,将 C、C++、OC 等高级语言进行编译优化,输出 IR 交给 LLVM 编译器后端,再进一步翻译成对应平台的底层语言。

    Get Your Hands Dirty

    编译你的 Clang

    截至本文执笔时,XCode 自带的 Clang 是不支持加载插件的,因此,想要在实际的项目中使用 Clang 插件,需要替换为自己编译的 Clang。
    跟着 官方文档 的步骤,按指定路径 checkout 好各个分支后,就可以编译 LLVM 了。需要注意的是,LLVM 不支持“原地编译”,需要另开一个文件夹作为 build 输出文件路径。编译 LLVM 的方式有多种,而本文使用的是 CMake,使用的指令是

    
                                                            
    cmake -G Xcode -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES:STRING=x86_64 -DLLVM_TARGETS_TO_BUILD=host -DLLVM_INCLUDE_TESTS=OFF -DCLANG_INCLUDE_TESTS=OFF -DLLVM_INCLUDE_UTILS=OFF -DLLVM_INCLUDE_DOCS=OFF -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_BUILD_EXTERNAL_COMPILER_RT=ON -DLIBCXX_INCLUDE_TESTS=OFF -DCOMPILER_RT_INCLUDE_TESTS=OFF -DCOMPILER_RT_ENABLE_IOS=OFF <llvm的源文件文件夹路径>
    
    
                                                        

    等待编译完成后,在输出的目录打开 LLVM.xcodeproj ,选择 ALL_BUILD scheme 进行编译,此处会有一个 compiler_rt 相关的 error ,尽管我全量 co 了所有的 LLVM 仓库,这块依然编译失败,尚不清楚原因,但这不影响后续插件的开发,故不做理会。

    接下来,你可以跟着 这篇文章,编写属于自己的 Clang 插件。我的建议是,动手让 Clang 插件跑起来就可以了,第 7 节之后的内容,快速阅读即可。(上文中的示例代码有一些问题,需要把 MobCodeConsumer 改成 MyPluginConsumer)。

    抽象语法树(AST)

    现在,你已经成功运行了你的第一个 Clang 插件,接下来让我们弄明白,如何通过 Clang AST,来对现有的代码进行分析。回想一下大学时期所学到的编译原理,亦或是直接在谷歌上搜索一下,对 AST 的解释大概是这么一张图 :

    语法树是编译器对我们所书写的代码的“理解”,如上图中的 x = a + b; 语句,编译器会先将 operator = 作为节点,将语句拆分为左节点和右节点,随后继续分析其子节点,直到叶子节点为止。对于一个基本的运算表达式,我想我们都能很轻松的写出它的 AST,但我们在日常业务开发时所写的代码,可不都是简单而基础的表达式而已,诸如

    
                                                            
    - (void)viewDidLoad{
        [self doSomething];
    }
    
    
                                                        

    这样的代码,其 AST 是什么样的呢?好消息是 Clang 提供了对应的命令,让我们能够输出 Clang 对特定文件编译所输出的 AST,先创建一个简单的 CommandLine 示例工程,在 main 函数之后如下代码:

    
                                                            
    @interface HelloAST : NSObject
    
    @end
    
    @implementation HelloAST
    
    - (void)hello{
        [self print:@"hello!"];
    }
    
    - (void)print:(NSString *)msg{
        NSLog(@"%@",msg);
    }
    
    @end
    
    
                                                        

    随后,在 Terminal 中进入 main.m 所在文件夹,执行如下指令:

    
                                                            
    clang -fmodules -fsyntax-only -Xclang -ast-dump main.m
    
    
                                                        

    让我们把目光定位到 import 语句之后的位置:

    我们可以看到一个清晰的树状结构,我们可以看到自己的类定义、方法定义、方法调用在 AST 中所对应的节点。

    其中第一个框为类定义,可以看到该节点名称为 ObjCInterfaceDecl,该类型节点为 objc 类定义(声明)。
    第二个框名称为 ObjCMethodDecl,说明该节点定义了一个 objc 方法(包含类、实例方法,包含普通方法和协议方法)。
    第三个框名称为 ObjCMessageExpr,说明该节点是一个标准的 objc 消息发送表达式([obj foo])。

    这些名称对应的都是 Clang 中定义的类,其中所包含的信息为我们的分析提供了可能。Clang 提供的各种类信息,可以在 这里 进行进一步查阅。

    同时,我们也看到在函数定义的时候,ImplicitParamDecl 节点声明了隐式参数 self 和 _cmd,这正是函数体内 self 关键字的来源。
    再把目光放到整个树的最顶部,我们可以看到根节点是 TranslationUnitDecl 的声明,由于 Clang 的语法树分析是基于单个文件的,所以该节点将会是我们所有分析的根节点。

    初步分析

    在一个 oc 的程序中,几乎所有代码都可以被划分为两类:Decl(声明),Stmt(语句),上述各个 ObjCXXXDecl 类都是 Decl 的子类,ObjCXXXExpr 也是 Stmt 的子类,根据 RecursiveASTVisitor 中声明的方法,我们可以看到对应的入口方法:bool VisitDecl (Decl *D) 以及 bool VisitStmt (Stmt *S),要知道如何这两个方法,我们还得先看看它们的实现,就拿 Decl 为例,在 RecusiveASTVisitor.h 中,我们可以看到如下代码:

    
                                                            
    //code 
    #define DEF_TRAVERSE_DECL(DECL, CODE)                                          \
      template <typename Derived>                                                  \
      bool RecursiveASTVisitor<Derived>::Traverse##DECL(DECL *D) {                 \
        bool ShouldVisitChildren = true;                                           \
        bool ReturnValue = true;                                                   \
        if (!getDerived().shouldTraversePostOrder())                               \
          TRY_TO(WalkUpFrom##DECL(D));                                             \
        { CODE; }                                                                  \
        if (ReturnValue && ShouldVisitChildren)                                    \
          TRY_TO(TraverseDeclContextHelper(dyn_cast<DeclContext>(D)));             \
        if (ReturnValue && getDerived().shouldTraversePostOrder())                 \
          TRY_TO(WalkUpFrom##DECL(D));                                             \
        return ReturnValue;                                                        \
      }
    
      //code 
        bool WalkUpFromDecl(Decl *D) { return getDerived().VisitDecl(D); }
        bool VisitDecl(Decl *D) { return true; }
    #define DECL(CLASS, BASE)                                                      \
      bool WalkUpFrom##CLASS##Decl(CLASS##Decl *D) {                               \
        TRY_TO(WalkUpFrom##BASE(D));                                               \
        TRY_TO(Visit##CLASS##Decl(D));                                             \
        return true;                                                               \
      }                                                                            \
      bool Visit##CLASS##Decl(CLASS##Decl *D) { return true; }
    
    
                                                        

    上面的几个宏,定义了以具体类名为方法名的各种 Visit 方法,而上下滑动,可以看到许多这样的定义:

    
                                                            
    DEF_TRAVERSE_DECL(ObjCInterfaceDecl, {
        ...
    })
    DEF_TRAVERSE_DECL(ObjCProtocolDecl, {// FIXME: implement
                                        })
    
    DEF_TRAVERSE_DECL(ObjCMethodDecl, {
        ...
    })
    
    
                                                        

    可以看出,我们如果想对某个特定的 XXXDecl 类进行分析,只需要实现 VisitXXXDecl(XXXDecl *D) 即可,而 VisitStmt 也可以使用类似方法,得到 Clang 回调。
    现在让我们小试牛刀,在所有类定义和方法调用的地方打出 Warning:

    
                                                            
    //statement
    bool VisitObjCMessageExpr(ObjCMessageExpr *expr){
        DiagnosticsEngine &D = Instance.getDiagnostics();
        int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Meet Msg Expr : %0");
        D.Report(expr->getLocStart(), diagID) << expr->getSelector().getAsString();
        return true;
    }
            
    //declaration
    bool VisitObjCMethodDecl(ObjCMethodDecl *decl){ // 包括了 protocol 方法的定义
        if (!isUserSourceCode(decl)){
            return true;
        }
        DiagnosticsEngine &D = Instance.getDiagnostics();
        int diagID = D.getCustomDiagID(DiagnosticsEngine::Warning, "Meet Method Decl : %0");
        D.Report(decl->getLocStart(), diagID) << decl->getSelector().getAsString();
        return true;
    }
    
    //helper
    bool isUserSourceCode (Decl *decl){
        std::string filename = Instance.getSourceManager().getFilename(decl->getSourceRange().getBegin()).str();
        
        if (filename.empty())
            return false;
        // /Applications/Xcode.app/xxx
        if(filename.find("/Applications/Xcode.app/") == 0)
            return false;
            
        return true;
    }
    
    
                                                        

    进行编译,现在在警告面板应该可以看到我们打出来的警告了。

    总结

    现在我们成功的编写了第一个 Clang 插件,弄清楚了 Clang AST 各个节点的意义,接入了 Clang 的回调方法,在下一篇文章中,我们将探索如何检查方法的有效性。

    参考资料

    深入剖析 iOS 编译 Clang LLVM
    使用Xcode开发iOS语法检查的Clang插件
    CLANG技术分享系列一:编写你的第一个CLANG插件
    A Brief Introduction to LLVM
    Clang.llvm.org

    展开全文
  • iOS 应用开发中,继承也是无法避开的话题。继承所带来的其中一项重要特性,便是多态(Polymorphism),多态特性也直接导致可继承方法实际作用对象的不确定性,我们可能实现一个虚基类,用于处理方法之间的调用...
  • 前言在上一篇文章中,我们了解了 AST 树的结构,并简单的实现了一个 RecursiveASTVisitor 子类,成功的访问了语法树上的各个节点。回头再看看一下前文的代码,结合官方文档,我们可以大致整理出如下调用逻辑:...
  • iOS 应用安装包瘦身

    2019-09-04 19:29:59
    iOS 安装包瘦身 1、App Thinning 苹果针对安装包已经做了三个层面的瘦身工作。 1.1 App Slicing 我们打包时,常选择arm64、armv7s 等架构支持。但是上传苹果应用商店后,他为我们的应用安装包针对不同的机型架构,...
  • iOS App应用瘦身

    2016-01-13 15:56:23
    App应用瘦身方法参考例子 1.设置arm http://blog.csdn.net/crazyzhang1990/article/details/38843465
  • iOS应用代码段瘦身办法 大型app应对苹果官方代码段大小限制的小伎俩… 背景 苹果官方文档对二进制 __TEXT 段大小有限制: 代码实在瘦不下去怎么办? 解决方案 利用rename_section过审核,在Xcode中向 ...
  • 应用包中的资源文件包括图片资源、音频资源、视频资源以及其他配置文件,这些往往占据了应用包中的很大一部分空间,在持续性开发过程中会不断添加新的功能模块,同时也会移除一些不再使用的模块,有时候我们不一定会...
  • iOS 【iOS应用代码段瘦身办法】

    千次阅读 2016-10-30 12:24:10
    大型app应对苹果官方代码段大小限制的小伎俩…
  • 为什么要给程序瘦身? 随着应用程序的功能越来越多,实现越来越复杂,第三方库的...因此在这些众多的问题下需要对应用进行瘦身处理。 一个应用程序由众多资源文件和可执行程序文件组成,资源文件的优化不在本文探...
  • iOS安装包瘦身

    2018-07-12 05:24:36
    最近打包应用时候发现个问题,同样的代码两个应用竟然打出来安装包大小不一样,一个拼车网26.4Mb, 另一个顺风车无论怎么对图片压缩打出来的包还是有33.9Mb 这么大,我查看了两个项目用到的第三方库和图片资源都是一样的...
  • iOS图片瘦身总结

    2015-09-25 15:49:00
    最近在公司写了个小程序来为iOS应用中的图片瘦身,进而减小APP大小,减少用户下载时的流量。 瘦身是在一个专门为图片瘦身的网站进行的。 地址:https://tinypng.com 这个网站提供的接口是基于https协议的,之前...
  • 应用瘦身”(App thinning) 是美国苹果公司自iOS 9发布的新特性,它能对Apple Store和操作系统进行优化,它根据用户的具体设备型号,在保证应用特性完整的前提下,尽可能地压缩和减少应用程序安装包的体积,也就是...
  • 当你从应用商店(App Store)下载应用时,请注意这点。iOS9 推出之后,大受欢迎。仅仅数周,已经有超过半数的 iOS 设备安装了这一新系统。这是 iOS 历代版本中采纳速度最快的—甚至打破了 2013 ...
  • apple在iOS9中引入了一套新的app瘦身方案,通过一些优化策略,将尽可能的减小app安装包的体积。这部分的大多工作是由app store来完成,开发者并不需要付出太多额外的开销。这篇博客将这部分内容做了简单的介绍,详细...
  • 最近项目中需要对应用的安装文件进行瘦身处理,所以对于应用瘦身的一些方法进行了整理,方便测试的时候,进行验证有相应的参考。 2. 应用瘦身总结思维导图: 3. 资料参考: 3.1 书籍 移动App性能评测与优化 ...

空空如也

空空如也

1 2 3 4 5
收藏数 89
精华内容 35
关键字:

ios应用瘦身