在快手做分享、無用類檢查、在廣州做 SwiftUI 學(xué)習(xí)筆記分享、InfoQ二叉樹視頻

在快手做分享

前滴滴同事邀請我去快手做分享。下面是分享時的 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)足夠精彩,余下的,定會更值得期待。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。