解决Swift + CocoaPods因动态库导致启动时间过长 iOS App启动流程

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看432 回复回复2 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 432|回复: 2
收起左侧

解决Swift + CocoaPods因动态库导致启动时间过长 iOS App启动流程

[复制链接]
swifter 发表于 2016-6-27 17:40:46 | 显示全部楼层 |阅读模式
快来登录
获取最新的苹果动态资讯
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
上周在敏捷大拇指上看到《Android从按下开机键到启动发生了什么》剖析了安卓手机启动过程,我回帖求知iOS启动过程。正好最近遇到了一个棘手的问题,就分析了iOS App启动过程(不是开机过程),我发现我的 App (iOS 9.2/iPhone 5s/Swift/CocoaPods)启动巨慢,通过 device log 一看,我的天呐!7.9s?

Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total time: 7.9 seconds (100.0%)  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total images loaded:  239 (216 from dyld shared cache)  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total segments mapped: 77, into 2811 pages with 236 pages pre-fetched  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total images loading time: 6.8 seconds (85.7%)  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total dtrace DOF registration time: 0.13 milliseconds (0.0%)  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total rebase fixups:  61,981  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total rebase fixups time: 63.21 milliseconds (0.8%)  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total binding fixups: 260,964  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total binding fixups time: 837.40 milliseconds (10.6%)  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total weak binding fixups time: 0.38 milliseconds (0.0%)  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total bindings lazily fixed up: 0 of 0  
Apr  2 21:33:27 Kittens-iPhone5S Cosmos[309] <Notice>: total time in initializers and ObjC setup: 213.3 milliseconds (2.7%)  
...

你可能会好奇如何查看启动时间,两步:

  • 添加环境变量 DYLD_PRINT_STATISTICS,设为 YES


解决 Swift + CocoaPods 因动态库导致启动时间过长 1

解决Swift + CocoaPods因动态库导致启动时间过长 iOS App启动流程 - 敏捷大拇指 - 解决 Swift + CocoaPods 因动态库导致启动时间过长 1


  • 开启 lib 的 log 输出


解决 Swift + CocoaPods 因动态库导致启动时间过长 2

解决Swift + CocoaPods因动态库导致启动时间过长 iOS App启动流程 - 敏捷大拇指 - 解决 Swift + CocoaPods 因动态库导致启动时间过长 2


随后通过 Device 窗口即可查看。

解决 Swift + CocoaPods 因动态库导致启动时间过长 3

解决Swift + CocoaPods因动态库导致启动时间过长 iOS App启动流程 - 敏捷大拇指 - 解决 Swift + CocoaPods 因动态库导致启动时间过长 3


先来回顾一下App 的启动流程(WWDC 2012 Session 235):

1)链接和载入。

  • 可以在 Time Profile 中显示 dyld 载入库函数,库会被映射到地址空间,同时完成绑定以及静态初始化。
  • 不必要的Framework,不要链接,或标记为 optinal。


2)UIKit 初始化。

  • 字体、状态栏、user defaults、main nib会被初始化。
  • User defaults 本质上是一个 plist 文件,保存的数据是同时被反序列化的,不要在 user defaults 里面保存图片等大数据。


3)application:didFinishLaunchingWithOptions 回调。

  • 尽量减少这里面的操作。一些不必要的操作(网络请求/数据库查询)可以放在首屏显示之后。


4)第一次 Core Animation 调用。

  • 在启动后的方法 [UIApplication _resportAppLaunchFinished] 中调用 CA::Transaction::commit 实现第一帧画面的绘制。


我们知道在 OC 项目中,程序的主入口是 main 函数。

[Objective-C] 纯文本查看 复制代码
int main(int argc, char * argv[])  
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil,
                   NSStringFromClass([AppDelegate class]));
    }
}


通过传入的参数 NSStringFromClass([AppDelegate class] 指定了 AppDelegate 类作为应用的委托。

而在 Swift 项目中也是需要 main 函数的,只是不是显式的,而是通过 @UIApplicationMain 将被标注的类作为委托,自动创建被标记的类并插入 main 函数。

[Objective-C] 纯文本查看 复制代码
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {  
...


如果你想在 Swift 中使用像 OC 中一样的 main 函数,可以参考 这篇文章

另外,iOS 系统有个叫看门狗的机制,超过这个机制规定时间的操作系统会自动掐掉。

启动
20秒
恢复运行
10秒
悬挂进程
10秒
启动
20秒
退出应用
6秒
后台运行
10分钟


| 注:Xcode在Debug的时候,会禁止“看门狗”。

回到一开始打印出的 Log,我们发现时间基本都耗在了 image load 上。可是凭着过往的经验,比我项目里图片多得多的 App 都不存在这个问题。有人说是 CocoaPods 导入库太多的关系,我第一时间觉得不太可能。于是我新建了一个空项目,只是引入了一些常用的官方 framework,竟然依然存在这个问题。国外貌似讨论得也比较激烈。也有人说 production 版本就没个问题。 我们都知道 iOS8 之后 Xcode 允许开发者自己创建动态库,但这有一个性能问题,就是分散的动态库将会动态地一个接一个导入从而拖慢项目启动时间。 (其实上面的 image load 是 dynamic framework 本身,并不是图片)。参考了 johnno1962 大神(此人乃 injectionforxcode 作者)的思路,解决办法就是把 Pods 作为一个整体静态引入,从而 dyld 不需要在启动时引入动态库。

1.先把 linker 中的 -framework 替换成 -filelist 。如果使用 CocoaPods,可以在 Podfile 里添加这段代码自动完成上面的操作:

[Shell] 纯文本查看 复制代码
post_install do |installer|  
  pods_target = installer.aggregate_targets.detect do |target|
    # Target label is either `Pods` or `Pods-#{name_of_your_main_target}` based on how complex your dependency graph is.
    target.label == "Pods"
  end

  puts '+ Removing framework dependencies'

  pods_target.xcconfigs.each_pair do |config_name, config|
    next if config_name == 'Test'
    config.other_linker_flags[:frameworks] = Set.new
    config.attributes['OTHER_LDFLAGS[arch=armv7]'] = '$(inherited) -filelist "$(OBJROOT)/Pods.build/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)-armv7.objects.filelist"'
    config.attributes['OTHER_LDFLAGS[arch=arm64]'] = '$(inherited) -filelist "$(OBJROOT)/Pods.build/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)-arm64.objects.filelist"'
    config.attributes['OTHER_LDFLAGS[arch=i386]'] = '$(inherited) -filelist "$(OBJROOT)/Pods.build/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)-i386.objects.filelist"'
    config.attributes['OTHER_LDFLAGS[arch=x86_64]'] = '$(inherited) -filelist "$(OBJROOT)/Pods.build/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)-x86_64.objects.filelist"'
    config.save_as(Pathname.new("#{pods_target.support_files_dir}/#{pods_target.label}.#{config_name}.xcconfig"))
  end

end


2.把 frame link 入 Embedded Binaries 。

解决 Swift + CocoaPods 因动态库导致启动时间过长 4

解决Swift + CocoaPods因动态库导致启动时间过长 iOS App启动流程 - 敏捷大拇指 - 解决 Swift + CocoaPods 因动态库导致启动时间过长 4


如果你使用 CocoaPods,在 Check Pods Manifest.lock 后创建一个 Run Script ,加入以下代码自动完成上述工作:

[Shell] 纯文本查看 复制代码
intermediates_directory = ENV['OBJROOT']  
configuration = ENV['CONFIGURATION']  
platform = ENV['EFFECTIVE_PLATFORM_NAME']  
archs = ENV['ARCHS']

archs.split(" ").each do |architecture|

Dir.chdir("#{intermediates_directory}/Pods.build") do

  filelist = ""

  Dir.glob("#{configuration}#{platform}/*.build/Objects-normal/#{architecture}/*.o") do |object_file|

    filelist += File.absolute_path(object_file) + "\n"

  end

  File.write("#{configuration}#{platform}-#{architecture}.objects.filelist", filelist)

end

end  


| 注:将 shell 的名字换成 !/usr/bin/ruby

不过,iOS 9.3 中貌似已经解决这个问题了。本文的意义其实也不大了,权当留个记录,万一你刚好遇到了同样的问题,兴许能帮到你。




参考链接







作者:KittenYang


都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

本帖被以下淘专辑推荐:

SwiftRobot 发表于 2016-7-3 19:02:05 | 显示全部楼层
库依赖是很麻烦的,所以才有了专门的管理软件啊
Caesar 发表于 2016-7-4 14:22:15 | 显示全部楼层
不要老指望main函数,好low的样子
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表