在快手做分享
前滴滴同事邀請我去快手做分享。下面是分享時的 Slides:
詳細文章介紹:如何對 iOS 啟動階段耗時進行分析 | 星光社 - 戴銘的博客
代碼:GitHub - ming1016/MethodTraceAnalyze: 方法耗時分析
無用類檢查
如果包里有一堆沒用的類,不光會影響用戶下載速度,也會影響啟動加載速度。檢查無用類,一次是無法獲得全部無用類的,因為無用的類里用了其他無用的類就算是有用了,所以需要進行遞歸查找,這樣才能夠連根拔起。這個過程如果是手動做比較費勁、收益無法一次評估,很難推動。同時還需要在線上灰度運行時檢查實際類的使用情況,很多靜態(tài)層面關(guān)聯(lián)的類使用,實際運行過程中也可能用不到。
思路和關(guān)鍵代碼如下。
第一步
使用 MethodTraceAnalyze 里 ParseOC 類的 ocNodes 函數(shù),通過傳入 workspace 路徑獲取所有節(jié)點的結(jié)構(gòu)體 OCNode。
let allNodes = ParseOC.ocNodes(workspacePath: workSpacePath)
找出類型是方法的結(jié)構(gòu)體,因為類的初始化和使用都是在這些方法中進行的。OCNode 針對不同類型所存儲的數(shù)據(jù)也是不同的,所以我定義一個 OCNodeValueProtocol 協(xié)議屬性,這樣就可以針對不同類型的節(jié)點存儲不同的數(shù)據(jù)。
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]
}
可以看到對方法類型會存所屬類、方法名和方法內(nèi)所有 token以便進行進一步分析。對類這種類型會記錄他的基類、類名、頭文件方法列表和實現(xiàn)文件方法列表,還用一個棧記錄繼承鏈。
第二步
獲取所有類的節(jié)點,通過對方法內(nèi)所有 token 的分析來看使用了哪些類,并記錄使用的類。
獲取所有類節(jié)點的代碼如下:
// 獲取所有類節(jié)點
var allClassSet:Set<String> = 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
記錄使用的類關(guān)鍵代碼:
static func parseAMethodUsedClass(node: OCNode, allClass: Set<String>) -> Set<String> {
var usedClassSet:Set<String> = 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
}
第三步
有了所有使用的類和所有的類,就能夠獲取沒用到的類。為了跑一次就能夠?qū)⑺袥]用的類找出,所以需要在找到無用類后,將這些類自動去掉再進行下一次查找。我這里寫了個遞歸來干這件事。具體代碼如下:
var recursiveCount = 0
func recursiveCheckUnUsedClass(unUsed:Set<String>) -> Set<String> {
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<String> = 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 {
// 如果發(fā)現(xiàn)還有無用的類,需要繼續(xù)遞歸調(diào)用進行分析
return recursiveCheckUnUsedClass(unUsed: unUsedClassSet)
}
return unUsedClassSet
}
// 遞歸調(diào)用
var unUsedClassFromRecursive = recursiveCheckUnUsedClass(unUsed: Set<String>())
通過遞歸進行多次能夠取到最終的結(jié)果。
第四步
對于繼承和系統(tǒng)的類還需要進行過濾,進一步提高準確性。
let unUsedClassSetCopy = unUsedClassFromRecursive
for aSet in unUsedClassSetCopy {
// 過濾系統(tǒng)控件
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)
}
}
清理了通過這種靜態(tài)掃描出的無用類,還可以通過運行時來判斷類是否被初始化了,從而找出無用類。類運行時是否初始化的這個信息是個布爾值,叫 isInitialized,存儲在元類 class_rw_t 結(jié)構(gòu)體的 flags 字段里,在 1<<29 位記錄。
完整代碼見 ParseOCMethodContent 文件:MethodTraceAnalyze/ParseOCMethodContent.swift at master · ming1016/MethodTraceAnalyze · GitHub
在廣州做的 SwiftUI 學(xué)習(xí)筆記分享
下面是筆記內(nèi)容:
推薦喵神的 SwiftUI 新書,ObjC 中國 - SwiftUI 與 Combine 編程。
這本介紹 Combine 的書也介紹的非常詳細:Using Combine
這個網(wǎng)站有大量 SwiftUI 的控件使用范例可以參考:SwiftUI by Example - free quick start tutorials for Swift developers
這個博客每篇都是 SwiftUI 相關(guān)的,而且更新非常頻繁:Home | Majid’s blog about Swift development
InfoQ二叉樹視頻
五分鐘的視頻,在導(dǎo)演構(gòu)思下需要一天在四個地方進行拍攝,由于前一天晚上慶功宴喝高了,拍攝當天 iPad 筆也忘帶了,頭還有些懵。導(dǎo)演中午飯都沒吃專門回家拿了他的筆給我用。下午地點安排在央美,先訪談再畫一張。北京電影學(xué)院畢業(yè)14年專業(yè)繪畫經(jīng)驗的導(dǎo)演賈成斌,在我畫時邊幫我改畫邊傳授了經(jīng)驗,我覺得這些經(jīng)驗會讓我更進一步。
下面是記者剡沛在 InfoQ 上發(fā)布的采訪內(nèi)容和視頻,原文在:“創(chuàng)造,就值得被肯定”,一名程序員的藝術(shù)人生丨二叉樹視頻
他是一名程序員,同時也用自己的業(yè)余時間畫畫。無論是技術(shù)分享還是珍藏回憶,他都用畫筆記錄自己,連接他人。他覺得程序員很酷,無論編程還是畫畫,都是在創(chuàng)造,這就是最值得肯定的事情。
他在高德負責架構(gòu)研發(fā)工作,也是大家眼中的藝術(shù)家,在他身上總能看到那些執(zhí)念與決心,它們發(fā)著光,無時無刻不影響著周圍的人。
他就是戴銘,一名酷酷的程序員。
當聊到”連接“這個詞的時候,他的眼神異常堅定。
他覺得自己堅持創(chuàng)作,堅持做很多沒有門檻的技術(shù)分享,很大一部分動力就來自這種渴望,渴望連接自己的過去,也渴望連接他人。
他就是戴銘,一個有點酷,還有點文藝的程序員,在高德地圖負責架構(gòu)研發(fā)工作。除了把自己活的很年輕,在他身上總能看到一些發(fā)著光的東西。
“是信念嗎?”
“是執(zhí)念。”
“當漫畫家,可能連飯都吃不飽。”
故事的開頭,多少有些遺憾。
戴銘最早接觸畫畫,是小學(xué)之前報過的一個高階國畫班,因為老師在上海,所以他每畫完一張都要寄過去并等待回信,當其中一幅畫改到第三遍的時候老師回信說:這孩子可能沒什么天賦。因為這件事,當時戴銘心里對畫畫的渴望,幾乎降到了冰點,對于畫畫的興趣也就此擱置。
直到六年級的一次美術(shù)作業(yè),平時酷愛看漫畫的戴銘,才再一次下定決心把自己喜歡的角色搬到紙上。
“同學(xué)都說畫的太像了,那種被再次肯定的開心,很難忘記。”
后來整個初中,戴銘都在課余時間畫漫畫,也沒再報班,一直到初中畢業(yè)戴銘跟父親說不想上學(xué)了,“想去畫漫畫,做一名漫畫家”。不難預(yù)料,這個想法并沒有得到父親的支持,“當漫畫家,可能連飯都吃不飽”。
但從戴銘的話語中,并沒有因為父親這次選擇而聽到絲毫氣餒,似乎心里的種子已經(jīng)生根。
“興趣不是說喜歡漫畫,就要去從事漫畫。當我們從被動的行為中獲得成就感時,也會在無形中培養(yǎng)出興趣,畫畫如此,編程也是一樣。“
“我不想讓自己投入了生命的熱愛,停滯不前。”
“從那后來,就一直在堅持畫畫了。”
從臨摹簡單的漫畫,到更復(fù)雜的畫風(fēng)、更多元的角色,再到臨摹寫實人物、影視劇照,期間還專門自學(xué)過素描,直到現(xiàn)在的再創(chuàng)作,戴銘除了把自己的愛好和回憶畫出來,還把自己的專業(yè)內(nèi)容做成漫畫,用更容易傳達的方式去做每一次技術(shù)分享。
“大概是從四、五年前開始,空閑的時候會花很長的時間畫畫,平時每天也會擠出一個多小時堅持練手,因為我不希望自己熱愛的東西,停滯不前,興趣不該只是興趣而已。”
后來戴銘接觸了數(shù)繪,就開始把很多創(chuàng)作留在屏幕上。
“用 ipad 畫,更適合我現(xiàn)在的角色,因為可以隨時開始和結(jié)束,不受環(huán)境和工具的影響,另一方面數(shù)字繪圖也讓他的作品在色彩方面,有了更多的提升空間。”
從容地掏出平板,只要自己想,就能隨時還原周遭的一切。這種感覺,就跟戴銘看待程序員時的表達一樣,“都是很酷的事情,因為無論程序員的人還是藝術(shù)家,他們都在創(chuàng)造新的事物,僅這一點,就值得被肯定。”
“工作不用心的人,生活也不會太精彩”
類似畫畫這種需要大量時間去“熬”的愛好,堅持總是最難的部分。
“時間永遠都是緊缺的,這是肯定。如果工作很忙,就先把時間全部投入到工作上,用最快的速度做好、做完,才能有更多的精力和心情去做其他事情”。
戴銘上一份工作在滴滴,剛?cè)肼毦拖M貙捵约旱哪芰Ψ秶瑤缀醭袚麄€部門的研發(fā)任務(wù),后來臨近發(fā)版 Bug 實在解不完,第二天來公司發(fā)現(xiàn)都被領(lǐng)導(dǎo)默默解掉了,才意識到一個人的力量始終有限,“一個手指,肯定比不過一個拳頭的力量。”
即便如此,戴銘也始終保持著對工作的熱血,當時間不夠用的時候,他會換個角度去看待問題。
“我覺得工作上面不上心、不拼命的人,生活也不會太精彩。工作是跟每個人的一生切實相關(guān)的事情,如果連這個都做不好,又如何能在其他事情上更用心的經(jīng)營?”
時間看透了,剩下的就是堅持。
當聊到堅持的原動力時,除了用回憶和分享去推動自己,在戴銘身上總散發(fā)著一股勁兒。一個興趣愛好而已,談信念可能過于悲壯,所以他認為,這股勁兒更像是決心和執(zhí)念。
有剛?cè)脒M入滴滴時,想肩扛所有工作的執(zhí)念;
有為了興趣上一個臺階,努力獲得央美朋友肯定的執(zhí)念;
也有怕自己的作品破壞了心中的完美角色,重復(fù)打磨的執(zhí)念。
結(jié)尾
戴銘是很強大的人,他講過的一段話令人印象深刻:
“幸福是面對過去,恐懼是面對未知的未來。我也忘記是從哪里看到的這句話,但我自己會這樣理解:回憶是讓人幸福的,未知是令人恐懼的。但如果我們沉湎在回憶中不敢面對未來,幸福始終是有限的,當我們用決心去面對未來,幸福就會越來越多,恐懼也會越來越少。”
裹著決心這把利劍,酷酷的戴銘就這樣用代碼和畫筆勾勒著自己的一生,而畫卷展開的部分就已經(jīng)足夠精彩,余下的,定會更值得期待。