在快手做分享
前滴滴同事邀请我去快手做分享。下面是分享时的 Slides:
详细文章介绍:如何对 iOS 启动阶段耗时进行分析 | 星光社 - 戴铭的博客
代码:GitHub - ming1016/MethodTraceAnalyze: 方法耗时分析
无用类检查
如果包里有一堆没用的类,不光会影响用户下载速度,也会影响启动加载速度。检查无用类,一次是无法获得全部无用类的,因为无用的类里用了其他无用的类就算是有用了,所以需要进行递归查找,这样才能够连根拔起。这个过程如果是手动做比较费劲、收益无法一次评估,很难推动。同时还需要在线上灰度运行时检查实际类的使用情况,很多静态层面关联的类使用,实际运行过程中也可能用不到。
思路和关键代码如下。
第一步
使用 MethodTraceAnalyze 里 ParseOC 类的 ocNodes 函数,通过传入 workspace 路径获取所有节点的结构体 OCNode。
let allNodes = ParseOC.ocNodes(workspacePath: workSpacePath)
找出类型是方法的结构体,因为类的初始化和使用都是在这些方法中进行的。OCNode 针对不同类型所存储的数据也是不同的,所以我定义一个 OCNodeValueProtocol 协议属性,这样就可以针对不同类型的节点存储不同的数据。
public struct OCNodeDefaultValue: OCNodeValueProtocol {
public var defaultValue: String
init() {
defaultValue = ""
}
}
public struct OCNodeMethod: OCNodeValueProtocol {
public var belongClass: String
public var methodName: String
public var tokenNodes: [OCTokenNode]
}
public struct OCNodeClass: OCNodeValueProtocol {
public var className: String
public var baseClass: String
public var hMethod: [String]
public var mMethod: [String]
public var baseClasses: [String]
}
可以看到对方法类型会存所属类、方法名和方法内所有 token以便进行进一步分析。对类这种类型会记录他的基类、类名、头文件方法列表和实现文件方法列表,还用一个栈记录继承链。
第二步
获取所有类的节点,通过对方法内所有 token 的分析来看使用了哪些类,并记录使用的类。
获取所有类节点的代码如下:
// 获取所有类节点
var allClassSet:Set = Set()
for aNode in allNodes {
if aNode.type == .class {
let classValue = aNode.value as! OCNodeClass
allClassSet.insert(classValue.className)
if classValue.baseClass.count > 0 {
baseClasses.insert(classValue.baseClass)
}
}
} // end for aNode in allNodes
记录使用的类关键代码:
static func parseAMethodUsedClass(node: OCNode, allClass: Set) -> Set {
var usedClassSet:Set = Set()
guard node.type == .method else {
return usedClassSet
}
let methodValue:OCNodeMethod = node.value as! OCNodeMethod
for aNode in methodValue.tokenNodes {
if allClass.contains(aNode.value) {
usedClassSet.insert(aNode.value)
}
}
return usedClassSet
}
第三步
有了所有使用的类和所有的类,就能够获取没用到的类。为了跑一次就能够将所有没用的类找出,所以需要在找到无用类后,将这些类自动去掉再进行下一次查找。我这里写了个递归来干这件事。具体代码如下:
var recursiveCount = 0
func recursiveCheckUnUsedClass(unUsed:Set) -> Set {
recursiveCount += 1
print("into recursive!!!!第\(recursiveCount)次")
print("----------------------\n")
for a in unUsed {
print(a)
}
var unUsedClassSet = unUsed
// 缩小范围
for aUnUsed in unUsedClassSet {
if allClassSet.contains(aUnUsed) {
allClassSet.remove(aUnUsed)
}
}
var allUsedClassSet:Set = Set()
for aNode in allNodes {
if aNode.type == .method {
let nodeValue:OCNodeMethod = aNode.value as! OCNodeMethod
// 过滤已判定无用类里的方法
guard !unUsedClassSet.contains(nodeValue.belongClass) else {
continue
}
let usedSet = ParseOCMethodContent.parseAMethodUsedClass(node: aNode, allClass: allClassSet)
if usedSet.count > 0 {
for aSet in usedSet {
allUsedClassSet.insert(aSet)
}
} // end if usedSet.count > 0
} // end if aNode.type == .method
} // end for aNode in allNodes
var hasUnUsed = false
// 找出无用类
for aSet in allClassSet {
if !allUsedClassSet.contains(aSet) {
unUsedClassSet.insert(aSet)
hasUnUsed = true
}
}
if hasUnUsed {
// 如果发现还有无用的类,需要继续递归调用进行分析
return recursiveCheckUnUsedClass(unUsed: unUsedClassSet)
}
return unUsedClassSet
}
// 递归调用
var unUsedClassFromRecursive = recursiveCheckUnUsedClass(unUsed: Set())
通过递归进行多次能够取到最终的结果。
第四步
对于继承和系统的类还需要进行过滤,进一步提高准确性。
let unUsedClassSetCopy = unUsedClassFromRecursive
for aSet in unUsedClassSetCopy {
// 过滤系统控件
let filters = ["NS","UI"]
var shouldFilter = false
for filter in filters {
if aSet.hasPrefix(filter) {
shouldFilter = true
}
}
// 过滤基类
if baseClasses.contains(aSet) {
shouldFilter = true
}
// 开始过滤
if shouldFilter {
unUsedClassFromRecursive.remove(aSet)
}
}
清理了通过这种静态扫描出的无用类,还可以通过运行时来判断类是否被初始化了,从而找出无用类。类运行时是否初始化的这个信息是个布尔值,叫 isInitialized,存储在元类 class_rw_t 结构体的 flags 字段里,在 1<<29 位记录。
完整代码见 ParseOCMethodContent 文件:MethodTraceAnalyze/ParseOCMethodContent.swift at master · ming1016/MethodTraceAnalyze · GitHub
在广州做的 SwiftUI 学习笔记分享
下面是笔记内容:
推荐喵神的 SwiftUI 新书,ObjC 中国 - SwiftUI 与 Combine 编程。
这本介绍 Combine 的书也介绍的非常详细:Using Combine
这个网站有大量 SwiftUI 的控件使用范例可以参考:SwiftUI by Example - free quick start tutorials for Swift developers
这个博客每篇都是 SwiftUI 相关的,而且更新非常频繁:Home | Majid’s blog about Swift development
InfoQ二叉树视频
五分钟的视频,在导演构思下需要一天在四个地方进行拍摄,由于前一天晚上庆功宴喝高了,拍摄当天 iPad 笔也忘带了,头还有些懵。导演中午饭都没吃专门回家拿了他的笔给我用。下午地点安排在央美,先访谈再画一张。北京电影学院毕业14年专业绘画经验的导演贾成斌,在我画时边帮我改画边传授了经验,我觉得这些经验会让我更进一步。
下面是记者剡沛在 InfoQ 上发布的采访内容和视频,原文在:“创造,就值得被肯定”,一名程序员的艺术人生丨二叉树视频
他是一名程序员,同时也用自己的业余时间画画。无论是技术分享还是珍藏回忆,他都用画笔记录自己,连接他人。他觉得程序员很酷,无论编程还是画画,都是在创造,这就是最值得肯定的事情。
他在高德负责架构研发工作,也是大家眼中的艺术家,在他身上总能看到那些执念与决心,它们发着光,无时无刻不影响着周围的人。
他就是戴铭,一名酷酷的程序员。
当聊到”连接“这个词的时候,他的眼神异常坚定。
他觉得自己坚持创作,坚持做很多没有门槛的技术分享,很大一部分动力就来自这种渴望,渴望连接自己的过去,也渴望连接他人。
他就是戴铭,一个有点酷,还有点文艺的程序员,在高德地图负责架构研发工作。除了把自己活的很年轻,在他身上总能看到一些发着光的东西。
“是信念吗?”
“是执念。”
“当漫画家,可能连饭都吃不饱。”
故事的开头,多少有些遗憾。
戴铭最早接触画画,是小学之前报过的一个高阶国画班,因为老师在上海,所以他每画完一张都要寄过去并等待回信,当其中一幅画改到第三遍的时候老师回信说:这孩子可能没什么天赋。因为这件事,当时戴铭心里对画画的渴望,几乎降到了冰点,对于画画的兴趣也就此搁置。
直到六年级的一次美术作业,平时酷爱看漫画的戴铭,才再一次下定决心把自己喜欢的角色搬到纸上。
“同学都说画的太像了,那种被再次肯定的开心,很难忘记。”
后来整个初中,戴铭都在课余时间画漫画,也没再报班,一直到初中毕业戴铭跟父亲说不想上学了,“想去画漫画,做一名漫画家”。不难预料,这个想法并没有得到父亲的支持,“当漫画家,可能连饭都吃不饱”。
但从戴铭的话语中,并没有因为父亲这次选择而听到丝毫气馁,似乎心里的种子已经生根。
“兴趣不是说喜欢漫画,就要去从事漫画。当我们从被动的行为中获得成就感时,也会在无形中培养出兴趣,画画如此,编程也是一样。“
“我不想让自己投入了生命的热爱,停滞不前。”
“从那后来,就一直在坚持画画了。”
从临摹简单的漫画,到更复杂的画风、更多元的角色,再到临摹写实人物、影视剧照,期间还专门自学过素描,直到现在的再创作,戴铭除了把自己的爱好和回忆画出来,还把自己的专业内容做成漫画,用更容易传达的方式去做每一次技术分享。
“大概是从四、五年前开始,空闲的时候会花很长的时间画画,平时每天也会挤出一个多小时坚持练手,因为我不希望自己热爱的东西,停滞不前,兴趣不该只是兴趣而已。”
后来戴铭接触了数绘,就开始把很多创作留在屏幕上。
“用 ipad 画,更适合我现在的角色,因为可以随时开始和结束,不受环境和工具的影响,另一方面数字绘图也让他的作品在色彩方面,有了更多的提升空间。”
从容地掏出平板,只要自己想,就能随时还原周遭的一切。这种感觉,就跟戴铭看待程序员时的表达一样,“都是很酷的事情,因为无论程序员的人还是艺术家,他们都在创造新的事物,仅这一点,就值得被肯定。”
“工作不用心的人,生活也不会太精彩”
类似画画这种需要大量时间去“熬”的爱好,坚持总是最难的部分。
“时间永远都是紧缺的,这是肯定。如果工作很忙,就先把时间全部投入到工作上,用最快的速度做好、做完,才能有更多的精力和心情去做其他事情”。
戴铭上一份工作在滴滴,刚入职就希望拓宽自己的能力范围,几乎承担整个部门的研发任务,后来临近发版 Bug 实在解不完,第二天来公司发现都被领导默默解掉了,才意识到一个人的力量始终有限,“一个手指,肯定比不过一个拳头的力量。”
即便如此,戴铭也始终保持着对工作的热血,当时间不够用的时候,他会换个角度去看待问题。
“我觉得工作上面不上心、不拼命的人,生活也不会太精彩。工作是跟每个人的一生切实相关的事情,如果连这个都做不好,又如何能在其他事情上更用心的经营?”
时间看透了,剩下的就是坚持。
当聊到坚持的原动力时,除了用回忆和分享去推动自己,在戴铭身上总散发着一股劲儿。一个兴趣爱好而已,谈信念可能过于悲壮,所以他认为,这股劲儿更像是决心和执念。
有刚入进入滴滴时,想肩扛所有工作的执念;
有为了兴趣上一个台阶,努力获得央美朋友肯定的执念;
也有怕自己的作品破坏了心中的完美角色,重复打磨的执念。
结尾
戴铭是很强大的人,他讲过的一段话令人印象深刻:
“幸福是面对过去,恐惧是面对未知的未来。我也忘记是从哪里看到的这句话,但我自己会这样理解:回忆是让人幸福的,未知是令人恐惧的。但如果我们沉湎在回忆中不敢面对未来,幸福始终是有限的,当我们用决心去面对未来,幸福就会越来越多,恐惧也会越来越少。”
裹着决心这把利剑,酷酷的戴铭就这样用代码和画笔勾勒着自己的一生,而画卷展开的部分就已经足够精彩,余下的,定会更值得期待。