另外还有一个现象,如果子工程和壳工程所依赖SDK的module没有对齐,lldb会很快断点生效,但是打印报错信息,同时无法po任何值。通过此现象也可以初步分析出来,在断点时lldb对子工程依赖的module进行了扫描。
但仅仅依赖表象分析还不够,所以后续的工作我们从两个方向着手,第一是从播放主业务模块的解耦测试,快速解耦播放主业务模块的外部依赖,测试耦合数量的减少对断点时间是否能有帮助;第二是从lldb自身断点原理的分析,看首次断点如此长的时间中lldb究竟在做什么动作。 通过业务模块解耦入手
我们通过删除及整理工程依赖引用代码的方式,快速清理外部模块依赖,最终将播放主业务模块的外部依赖降到90个左右。整理完毕后,播放主业务首次调试断点时间也从200秒左右降到120秒左右,对团队开发困难现状有所缓解。但是经过实际验证和应用后,我们也发现这种依赖业务层解耦的方式是对于团队来说不可行的,根本原因有二: 1、改造成本高
播放主业务模块从200多个模块依赖降到了90多个,一方面来说说对于防止工程腐化起到了积极帮助,另一方面在业务需求的压力下,研发人员需要投入了巨大的精力来进行代码重构和解耦。长期来看,不同垂直业务团队面临的情况不同,未来的业务技术需求复杂度也不尽相同,这个方案是无法做到快速复用。从人力成本来说,这个方案只能短期进行工程治理,无法长期坚持下去。 2、实际收益低
从获得的收益来看,播放主业务模块外部依赖降低到90多个后,我们原来的预期是调试首次断点时间能降低50%甚至更低,但是结果来看,在外部依赖已经无法解除的情况下,首次断点等待时间依然长达120秒以上,这样的收益结果是我们无法接受的。因此也得出来结论,在优酷iOS这样大型组件化多工程的模式下,我们用业务模块解耦的方式是无法根治该问题的。 通过LLDB分析入手
经过工程治理后,我们觉得还是应该从正面攻克该问题,从LLDB分析来查看根本原因并且解决。如果要分析LLDB入手,对于工程师来说最好的办法还是查看Swift源码,跑起来看一看内部的原型机制。我们首先根据苹果的文档将源码下载下来,然后进行配置,具体文档可以参考 How to Set Up an Edit-Build-Test-Debug Loop,一步一步的跟着做就可以。
由于Swift是依赖于LLVM,并且在其基础上做了自己的定制化开发,所以切换分支不能只切换Swift源码的,需要将LLVM一起切到对应的分支上, 保证代码同步。正好Swift提供了相应的工具来帮助我们切换对应分支,只需要运行Swift文件下的utils/update-checkout相关命令即可。优酷iOS团队目前使用的是Swift5.4版本,对应Xcode版本为13.2.1。 1、使用LLVM自带耗时工具
想要看到底在断点命中后,到底哪块最耗时,就需要使用工具来计算耗时,而这块LLVM有自带的工具类TimeProfiler,里面封装了计时方法,并且输出相关json文件,然后可以用chrome自带的tracing工具解析后现实相关图表
针对这个参数,我们进行了尝试,在Xcode构建设置里的Other Swift Flags里加上这个参数,但是从结果发现也没生效。于是我们再次查内外部资料,并且在官方Swift论坛发帖进行咨询,这其中有个外国的iOS开发者回复表示需要添加自定义flag SWIFT_SERIALIZE_DEBUGGING_OPTIONS=NO。随后我们立刻在Xcode工程里加上该选项后并进行验证,从实际结果来说,首次断点速度获得了显著的提升,但也同时发现了严重的缺陷。当团队同学想要po打印相关变量的时候,却什么都打不出来,lldd直接无法解析,从实际开发角度来说该方案不行。 无效方案2 - 对依赖库的修改
在我们自己构建的lldb去调试工程的时候,由于编译的lldb是debug包,当命中断点后,lldb会打印一些debug的log信息。这其中有一堆log非常引人注目,会持续地打好几十秒,因此我们立刻对这部份log俩进行分析,下面是部分截取的log:
warning: (arm64) /Users/ray/workspace/YouKuUniversal/Pods/SOME/SOME.framework/SOME(SOME9999999.o) 0x00004c50: unable to locate module needed for external types: /Users/remoteserver/build/14695183/workspace/iphone-out/ModuleCache.noindex/2YQ3UYLF0BE3R/UIKit-1XGSPECLTDLOB.pcm
error: '/Users/remoteserver/build/14695183/workspace/iphone-out/ModuleCache.noindex/2YQ3UYLF0BE3R/UIKit-1XGSPECLTDLOB.pcm' does not exist
Debugging will be degraded due to missing types. Rebuilding the project will regenerate the needed module files.
memory-module-load-level -- Loading modules from memory can be
slow as reading the symbol tables and
other data can take a long time
depending on your connection to the
debug target. This setting helps users
control how much information gets
loaded when loading modules from
memory.'complete' is the default value
for this setting which will load all
sections and symbols by reading them
from memory (slowest, most accurate).
'partial' will load sections and
attempt to find function bounds
without downloading the symbol table
(faster, still accurate, missing
symbol names). 'minimal' is the
fastest setting and will load section
data with no symbols, but should
rarely be used as stack frames in
these memory regions will be
inaccurate and not provide any context
(fastest).
public func aliprint(_ target:Any?,selector:String?){
if let target = target as AnyObject?{
if let selector = selector {
let returnValue = target.perform(NSSelectorFromString(selector))
print("(String(describing: returnValue?.takeUnretainedValue()))")
}else{
print("(String(describing: target))")
}
}
}