├── MethodTraceAnalyze ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── OC │ ├── ParseOCClass.swift │ ├── ParseOC.swift │ ├── Apply │ │ ├── OCApplyFeature.swift │ │ └── FindWhoUseClassAndFunction.swift │ ├── TestOC.swift │ ├── MachO │ │ └── ParseMachO.swift │ ├── ModifyOCContent.swift │ ├── OCStatistics.swift │ ├── ParseOCTokens.swift │ ├── ParseOCTokensDefine.swift │ └── ParseOCNodes.swift ├── Data │ ├── contents.xcworkspacedata │ ├── test.json │ ├── subscriptionSimple.xml │ └── AppDelegate.txt ├── MethodTraceAnalyze.entitlements ├── Core │ ├── Dictionary.swift │ ├── Test.swift │ ├── Handy.swift │ ├── Token.swift │ ├── FileHandle.swift │ └── Lexer.swift ├── Config │ └── Config.swift ├── UI │ ├── SUIText.swift │ └── SUITextField.swift ├── Info.plist ├── ContentView.swift ├── Xcodeproj │ ├── XcodeProjectParse.swift │ ├── ParseXcodeprojTreeNode.swift │ ├── ParseXcodeprojSource.swift │ ├── ParseXcodeprojTokens.swift │ └── ParseXcodeprojNode.swift ├── AppDelegate.swift ├── XML │ ├── ParseStandXML.swift │ ├── ParseStandXMLTagTokens.swift │ ├── ParseStandXMLTags.swift │ └── TestXML.swift ├── JSON │ ├── ParseJSONTokens.swift │ ├── ParseJSONItem.swift │ └── TestJSON.swift └── Launch │ └── ParseLaunchJSON.swift ├── README.md ├── MethodTraceAnalyze.xcodeproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── MethodTraceAnalyzeTests ├── Info.plist └── MethodTraceAnalyzeTests.swift ├── MethodTraceAnalyzeUITests ├── Info.plist └── MethodTraceAnalyzeUITests.swift ├── .gitignore └── LICENSE /MethodTraceAnalyze/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MethodTraceAnalyze 2 | 方法耗时分析 3 | 4 | 介绍参见:https://ming1016.github.io/2019/12/07/how-to-analyze-startup-time-cost-in-ios/ 5 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/ParseOCClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseOCClass.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2020/1/8. 6 | // Copyright © 2020 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | -------------------------------------------------------------------------------- /MethodTraceAnalyze.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Data/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | testValue 9 | 10 | 11 | -------------------------------------------------------------------------------- /MethodTraceAnalyze.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/MethodTraceAnalyze.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.downloads.read-write 8 | 9 | com.apple.security.files.user-selected.read-write 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Core/Dictionary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/10/28. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Dictionary where Value:Comparable { 12 | var sortedByValue:[(Key,Value)] {return Array(self).sorted{$0.1 < $1.1}} 13 | } 14 | extension Dictionary where Key:Comparable { 15 | var sortedByKey:[(Key,Value)] {return Array(self).sorted{$0.0 < $1.0}} 16 | } 17 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Core/Test.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Test.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/9/25. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol Test { 12 | static func cs(current:String, expect:String, des:String) 13 | } 14 | 15 | // compare string 对比两个字符串值 16 | extension Test { 17 | static func cs(current:String, expect: String, des: String) { 18 | if current == expect { 19 | print("✅ \(des) ok,符合预期值:\(expect)") 20 | } else { 21 | let msg = "❌ \(des) fail,不符合预期值:\(expect)" 22 | print(msg) 23 | assertionFailure(msg) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Data/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "value1", 3 | "key2": 22, 4 | "key3": { 5 | "subKey1": "subValue1", 6 | "subKey2": 40, 7 | "subKey3":[ 8 | { 9 | "sub1Key1": 10, 10 | "sub1Key2":{ 11 | "sub3Key1": "sub3Value1", 12 | "sub3Key2": "sub3Value2" 13 | } 14 | }, 15 | { 16 | "sub1Key1": 11, 17 | "sub1Key2": 15 18 | } 19 | ], 20 | "subKey4": [ 21 | "value1", 22 | 23, 23 | "value2" 24 | ], 25 | "subKey5": 2 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Core/Handy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Handy.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/11/1. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func delay(interval: TimeInterval, closure: @escaping () -> Void) { 12 | DispatchQueue.main.asyncAfter(deadline: .now() + interval) { 13 | closure() 14 | } 15 | } 16 | 17 | func nowDateFormat() -> String { 18 | let now = Date() 19 | let format = DateFormatter() 20 | format.dateFormat = "yyyy年MM月dd日 HH:mm:ss" 21 | 22 | return format.string(from: now) 23 | } 24 | 25 | func nowTimeInterval() -> Int { 26 | return Int(Date().timeIntervalSince1970) 27 | } 28 | -------------------------------------------------------------------------------- /MethodTraceAnalyzeTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MethodTraceAnalyzeUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Config/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2019/12/11. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Config: String { 12 | case downloadPath = "/Users/ming/Downloads/" 13 | 14 | case aMFilePath = "/Users/ming/Downloads/GCDFetchFeed/GCDFetchFeed/GCDFetchFeed/ArtWork.h" 15 | 16 | // startTrace、trace_15s1114 17 | case traceJSON = "startTrace" 18 | 19 | case classBundleOwner = "/Users/ming/Downloads/data/Biz/ClassBundle1025.csv" 20 | case classBundleOnwerOrigin = "/Users/ming/Downloads/data/Biz/ClassAndBundle.csv" 21 | // /Users/ming/Downloads/data/BundleSize/Simple 22 | // /Users/ming/Downloads/data/BundleSize/BigProject 23 | case macho = "/Users/ming/Downloads/data/BundleSize/GCDFetchFeed" 24 | 25 | static func workPath() -> String { 26 | return FileHandle.fileContent(path: "/Users/ming/Downloads/data/Config/workpath.txt") 27 | } 28 | } 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/UI/SUIText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SUIText.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2020/2/20. 6 | // Copyright © 2020 ming. All rights reserved. 7 | // 8 | import SwiftUI 9 | 10 | import Foundation 11 | 12 | struct AddressView: View { 13 | var address: (one:String,two:String,three:String) 14 | 15 | var body: some View { 16 | VStack(alignment: .leading) { 17 | 18 | Group { 19 | Text(address.one) 20 | Text(address.two) 21 | }.font(.headline) 22 | .padding(3) 23 | .background(Color.secondary) 24 | 25 | HStack { 26 | Text(address.three) 27 | Text(address.one) 28 | } 29 | Text(address.two) 30 | } 31 | } 32 | } 33 | 34 | struct TitleView: View { 35 | var title: String 36 | 37 | var body: some View { 38 | Text(title) 39 | .font(.headline) 40 | .italic() 41 | .foregroundColor(.blue) 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /MethodTraceAnalyzeTests/MethodTraceAnalyzeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MethodTraceAnalyzeTests.swift 3 | // MethodTraceAnalyzeTests 4 | // 5 | // Created by ming on 2019/11/30. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import MethodTraceAnalyze 11 | 12 | class MethodTraceAnalyzeTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /MethodTraceAnalyze/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2019 ming. All rights reserved. 27 | NSMainStoryboardFile 28 | Main 29 | NSPrincipalClass 30 | NSApplication 31 | NSSupportsAutomaticTermination 32 | 33 | NSSupportsSuddenTermination 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2019/11/30. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | let colors: [Color] = [.red, .green, .blue] 13 | 14 | var body: some View { 15 | VStack { 16 | Text("text") 17 | .frame(maxWidth: .infinity, maxHeight: .infinity) 18 | // TitleView(title: "title view") 19 | // AddressView(address: (one: "one", two: "two", three: "three")) 20 | // SignUpForm() 21 | Text("which").font(.custom("AmericanTypewriter", size: 72)) 22 | 23 | // Divider() 24 | Text("which").font(.custom("AmericanTypewriter", size: 72)) 25 | ZStack { 26 | Rectangle() 27 | .fill(Color.green) 28 | .frame(width: 50, height: 50) 29 | .zIndex(1) 30 | 31 | Rectangle() 32 | .fill(Color.red) 33 | .frame(width: 100, height: 100) 34 | } 35 | VStack(alignment: .leading) { 36 | ForEach((1...10).reversed(), id: \.self) { 37 | Text("\($0)…") 38 | } 39 | 40 | Text("Ready or not, here I come!") 41 | } 42 | ForEach(colors, id: \.self) { color in 43 | Text(color.description.capitalized) 44 | .padding() 45 | .background(color) 46 | } 47 | } 48 | } 49 | } 50 | 51 | 52 | -------------------------------------------------------------------------------- /MethodTraceAnalyzeUITests/MethodTraceAnalyzeUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MethodTraceAnalyzeUITests.swift 3 | // MethodTraceAnalyzeUITests 4 | // 5 | // Created by ming on 2019/11/30. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class MethodTraceAnalyzeUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | 16 | // In UI tests it is usually best to stop immediately when a failure occurs. 17 | continueAfterFailure = false 18 | 19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 20 | } 21 | 22 | override func tearDown() { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | } 25 | 26 | func testExample() { 27 | // UI tests must launch the application that they test. 28 | let app = XCUIApplication() 29 | app.launch() 30 | 31 | // Use recording to get started writing UI tests. 32 | // Use XCTAssert and related functions to verify your tests produce the correct results. 33 | } 34 | 35 | func testLaunchPerformance() { 36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) { 37 | // This measures how long it takes to launch your application. 38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) { 39 | XCUIApplication().launch() 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/ParseOC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseOC.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2019/12/6. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class ParseOC { 12 | public static func ocNodes(workspacePath:String) -> [OCNode] { 13 | var allPath = XcodeProjectParse.allSourceFileInWorkspace(path: workspacePath) 14 | // for path in allPath { 15 | // // 16 | // print(path) 17 | // } 18 | var allNodes = [OCNode]() 19 | 20 | let groupCount = 60 // 一组容纳个数 21 | let groupTotal = allPath.count/groupCount + 1 22 | 23 | var groups = [[String]]() 24 | for i in 0.. 0 { 32 | groups.append(group) 33 | } 34 | } 35 | 36 | for group in groups { 37 | let dispatchGroup = DispatchGroup() 38 | 39 | for node in group { 40 | dispatchGroup.enter() 41 | let queue = DispatchQueue.global() 42 | queue.async { 43 | let ocContent = FileHandle.fileContent(path: node) 44 | let node = ParseOCNodes(input: ocContent, filePath: node).parse() 45 | for aNode in node.subNodes { 46 | allNodes.append(aNode) 47 | } 48 | dispatchGroup.leave() 49 | 50 | } // end queue async 51 | } // end for 52 | dispatchGroup.wait() 53 | } // end for 54 | 55 | 56 | return allNodes 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Xcodeproj/XcodeProjectParse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XcodeProjectParse.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/8/20. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class XcodeProjectParse { 12 | 13 | 14 | // MARK: 获取 Workspace 里所有的源文件。 15 | public static func allSourceFileInWorkspace(path: String) -> [String] { 16 | var pathArr = path.split(separator: "/") 17 | pathArr.removeLast() 18 | let pathStr = pathArr.joined(separator: "/") 19 | 20 | let allFiles = FileHandle.allFilePath(path: path) 21 | if allFiles.count == 0 { 22 | return [String]() 23 | } 24 | 25 | var allSourceFile = [String]() // 获取的所有源文件路径 26 | 27 | for aFile in allFiles { 28 | if aFile.fileName == "contents.xcworkspacedata" { 29 | let root = ParseStandXML(input: aFile.content).parse() 30 | let workspace = root.subNodes[1] 31 | 32 | for fileRef in workspace.subNodes { 33 | var fileRefPath = fileRef.attributes[0].value 34 | fileRefPath.removeFirst(6) 35 | 36 | // 判断是相对路径还是绝对路径 37 | let arr = fileRefPath.split(separator: "/") 38 | var projectPath = "" 39 | if arr.count > 2 { 40 | projectPath = "\(fileRefPath)/project.pbxproj" 41 | } else { 42 | projectPath = "/\(pathStr)/\(fileRefPath)/project.pbxproj" 43 | } 44 | // 读取 project 文件内容分析 45 | 46 | allSourceFile += ParseXcodeprojSource(input: projectPath).parseAllFiles() 47 | 48 | } // end for fileRef in workspace.subNodes 49 | 50 | } // end for 51 | } // end for 52 | 53 | return allSourceFile 54 | 55 | } // end for 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/UI/SUITextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SUITextField.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2020/2/20. 6 | // Copyright © 2020 ming. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SignUpForm: View { 12 | @State private var username = "" 13 | @State private var email = "" 14 | 15 | var body: some View { 16 | Form { 17 | Text("Sign up").font(.headline) 18 | TextField("Username", text: $username) 19 | .modifier(Validation(value: username) { name in 20 | name.count > 4 21 | }) 22 | .prefixedWithIcon(named: "person.circle.fill") 23 | TextField("Email", text: $email) 24 | .prefixedWithIcon(named: "envelope.circle.fill") 25 | Button( 26 | action: { print("ok ") }, 27 | label: { Text("Continue") } 28 | ) 29 | } 30 | } 31 | } 32 | 33 | // 使用扩展添加复用组件 34 | extension View { 35 | func prefixedWithIcon(named name: String) -> some View { 36 | HStack { 37 | Image(name) 38 | self 39 | } 40 | } 41 | } 42 | 43 | // ViewModifier 来构建链式的视图验证 44 | struct Validation: ViewModifier { 45 | var value: Value 46 | var validator: (Value) -> Bool 47 | 48 | func body(content: Content) -> some View { 49 | // Here we use Group to perform type erasure, to give our 50 | // method a single return type, as applying the 'border' 51 | // modifier causes a different type to be returned: 52 | Group { 53 | if validator(value) { 54 | content.border(Color.green) 55 | } else { 56 | content 57 | } 58 | } 59 | } 60 | } 61 | 62 | struct IconPrefixedTextField: View { 63 | var iconName: String 64 | var title: String 65 | @Binding var text: String 66 | 67 | var body: some View { 68 | HStack { 69 | Image(iconName) 70 | TextField(title, text: $text) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/Apply/OCApplyFeature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OCApplyFeature.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2020/2/14. 6 | // Copyright © 2020 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct OCApplyFeature { 12 | 13 | //MARK: Bundle 和 Class 的关系 14 | static func loadClassBundleOwner() -> [String:(String,String)] { 15 | 16 | let content = FileHandle.fileContent(path: Config.classBundleOwner.rawValue) 17 | 18 | let tokens = Lexer(input: content, type: .plain).allTkFast(operaters: ",") 19 | var allTks = [[Token]]() 20 | var currentTks = [Token]() 21 | for tk in tokens { 22 | if tk == .newLine { 23 | allTks.append(currentTks) 24 | currentTks = [Token]() 25 | continue 26 | } 27 | if tk == .id(",") { 28 | continue 29 | } 30 | currentTks.append(tk) 31 | } 32 | 33 | var classBundleOwnerDic = [String:(String,String)]() 34 | for tks in allTks { 35 | var i = 0 36 | var currentClass = "" 37 | var currentBundle = "" 38 | var currentOwner = "" 39 | for tk in tks { 40 | if i == 0 { 41 | // class 42 | currentClass = tk.des() 43 | } 44 | if i == 1 { 45 | // bundle 46 | currentBundle = tk.des() 47 | } 48 | if i == 2 { 49 | // owner 50 | currentOwner = tk.des() 51 | } 52 | i += 1 53 | } 54 | classBundleOwnerDic[currentClass] = (currentBundle,currentOwner) 55 | } 56 | return classBundleOwnerDic 57 | } 58 | 59 | // MARK: 从 Name 里取出 Class 60 | public static func bundleAndClassFromName(name:String) -> String { 61 | let s1Arr = name.components(separatedBy: "[") 62 | let className = s1Arr[1].components(separatedBy: "]")[0] 63 | return className 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2019/11/30. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import SwiftUI 11 | 12 | @NSApplicationMain 13 | class AppDelegate: NSObject, NSApplicationDelegate { 14 | 15 | var window: NSWindow! 16 | 17 | 18 | func applicationDidFinishLaunching(_ aNotification: Notification) { 19 | // Create the SwiftUI view that provides the window contents. 20 | let contentView = ContentView() 21 | 22 | // Create the window and set the content view. 23 | window = NSWindow( 24 | contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), 25 | styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], 26 | backing: .buffered, defer: false) 27 | window.center() 28 | window.setFrameAutosaveName("Main Window") 29 | window.contentView = NSHostingView(rootView: contentView) 30 | window.makeKeyAndOrderFront(nil) 31 | export() 32 | } 33 | 34 | func export() { 35 | ParseMachO.run() 36 | 37 | // TestJSON.codeLines() 38 | // TestJSON.testJSON() 39 | // TestXML.testXcodeWorkspace() 40 | // TestXcodeproj.testSection() 41 | 42 | // TestOC().testWorkspace() 43 | 44 | // TestOC.testOC() 45 | // TestOC.testMethodCall(filePath: Config.aMFilePath.rawValue) 46 | // TestOC.testUnUsedClass(filePath: Config.aMFilePath.rawValue) 47 | // LaunchJSON.exportAll() 48 | 49 | // TestOC.testM(filePath: Config.aMFilePath.rawValue) 50 | // FindWhoUseClassAndFunction.whoUseClass(workSpacePath: Config.workPath(), className: "UIAlertView") 51 | // let _ = ParseOCMethodContent.unUsedClass(workSpacePath: Config.workPath()) 52 | // DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { 53 | // OCStatistics.showAll() 54 | // } 55 | 56 | } 57 | 58 | func applicationWillTerminate(_ aNotification: Notification) { 59 | // Insert code here to tear down your application 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Data/subscriptionSimple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 星光社 - 戴铭的博客 5 | 6 | 7 | 8 | 9 | 2019-07-28T16:52:44.082Z 10 | http://ming1016.github.io/ 11 | 12 | 13 | 戴铭 14 | 15 | 16 | 17 | Hexo 18 | 19 | 20 | iOS 开发舆图 21 | 22 | http://ming1016.github.io/2019/07/29/ios-map/ 23 | 2019-07-29T04:49:06.000Z 24 | 2019-07-28T16:52:44.082Z 25 | 26 | 43篇 《iOS开发高手课》已完成,后面会对内容进行迭代,丰富下内容和配图。最近画了张 iOS 开发全景舆图,还有相关一些资料整理,方便我平时开发 App 时参看。舆图如下:

27 |






28 |

接下来,我按照 iOS 开发地图的顺序,和你推荐一些相关的学习资料。

29 |

实例

学习 iOS 开发最好是从学习一个完整的 App 入手,GitHub上的Open-Source iOS Apps
项目,收录了大量开源的完整 App 例子,比如 Hacker News Reader 等已经上架了 App Store 的应用程序,所有例子都会标注是否上架 App Store的、所使用开发语言、推荐等级等信息,有利于进行选择学习。

30 | ]]>
31 | 32 | 33 | 34 | <p>43篇 <a href="https://time.geekbang.org/column/intro/161" target="_blank" rel="external">《iOS开发高手课》</a>已完成,后面会对内容进行迭代,丰富下内容和配图。最近画了张 iOS 开 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 |
48 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Core/Token.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Token.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/8/5. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum Token { 12 | case eof 13 | case newLine 14 | case space 15 | case comments(String) // 注释 16 | case constant(Constant) // float、int 17 | case id(String) // string 18 | case string(String) // 代码中引号内字符串 19 | 20 | func des() -> String { 21 | switch self { 22 | case .space: 23 | return " " 24 | case let .comments(commentString): 25 | return commentString 26 | case let .constant(.float(float)): 27 | return "\(float)" 28 | case let .constant(.integer(int)): 29 | return "\(int)" 30 | case let .constant(.string(string)): 31 | return string 32 | case let .id(idString): 33 | return idString 34 | case let .string(sString): 35 | return sString 36 | default: 37 | return "" 38 | } 39 | } 40 | } 41 | 42 | 43 | 44 | extension Token: Equatable { 45 | public static func == (lhs: Token, rhs: Token) -> Bool { 46 | switch (lhs, rhs) { 47 | case (.eof, .eof): 48 | return true 49 | case (.newLine, .newLine): 50 | return true 51 | case (.space, .space): 52 | return true 53 | case let (.constant(left), .constant(right)): 54 | return left == right 55 | case let (.comments(left), .comments(right)): 56 | return left == right 57 | case let (.id(left), .id(right)): 58 | return left == right 59 | case let (.string(left), .string(right)): 60 | return left == right 61 | default: 62 | return false 63 | } 64 | } 65 | } 66 | 67 | public enum Constant { 68 | case string(String) 69 | case integer(Int) 70 | case float(Float) 71 | case boolean(Bool) 72 | } 73 | 74 | extension Constant: Equatable { 75 | public static func == (lhs: Constant, rhs: Constant) -> Bool { 76 | switch (lhs, rhs) { 77 | case let (.integer(left), .integer(right)): 78 | return left == right 79 | case let (.string(left), .string(right)): 80 | return left == right 81 | case let (.float(left), .float(right)): 82 | return left == right 83 | case let (.boolean(left), .boolean(right)): 84 | return left == right 85 | default: 86 | return false 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Core/FileHandle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileHandle.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/8/2. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class FileHandle { 12 | 13 | public static func allFilePath(path: String) -> [File] { 14 | let fileManager = FileManager.default 15 | let allPath = fileManager.enumerator(atPath: path) 16 | guard let allfiles = allPath?.allObjects else { 17 | print("path no found") 18 | return [] 19 | } 20 | var reAllFiles = [File]() 21 | for afilePath in allfiles { 22 | let aFileFullPath = "\(path)/\(afilePath)" 23 | do { 24 | let content = try String(contentsOfFile: aFileFullPath, encoding: String.Encoding.utf8) 25 | reAllFiles.append(File(fileName: "\(afilePath)", path: aFileFullPath, content: content)) 26 | } catch { 27 | continue 28 | } 29 | } 30 | return reAllFiles 31 | } 32 | 33 | // 根据文件路径返回文件内容 34 | public static func fileContent(path: String) -> String { 35 | do { 36 | return try String(contentsOfFile: path, encoding: String.Encoding.utf8) 37 | } catch { 38 | return "" 39 | } 40 | } 41 | 42 | public static func fileSave(content:String, path:String) { 43 | do { 44 | try content.write(to: URL(fileURLWithPath: path), atomically: false, encoding: String.Encoding.utf8) 45 | } catch { 46 | // 47 | } 48 | } 49 | 50 | // 保存文件到下载目录 51 | public static func writeToDownload(fileName: String, content: String) { 52 | try! content.write(toFile: "\(Config.downloadPath.rawValue)\(fileName)", atomically: true, encoding: String.Encoding.utf8) 53 | } 54 | 55 | public static func handlesFiles(allfilePath:[String], handle:@escaping(String,String)->Void) { 56 | let handleBlock = handle 57 | let groupCount = 60 58 | let groupTotal = allfilePath.count / groupCount + 1 59 | 60 | var groups = [[String]]() 61 | for i in 0.. 0 { 69 | groups.append(group) 70 | } 71 | } // end for 72 | 73 | for group in groups { 74 | let dispatchGroup = DispatchGroup() 75 | 76 | for node in group { 77 | dispatchGroup.enter() 78 | let queue = DispatchQueue.global() 79 | 80 | queue.async { 81 | let content = FileHandle.fileContent(path: node) 82 | handleBlock(node, content) 83 | dispatchGroup.leave() 84 | } // end queue async 85 | } // end for 86 | dispatchGroup.wait() 87 | } // end for 88 | } // end func handlesFiles 89 | } 90 | 91 | 92 | public struct File { 93 | public let fileName:String // 文件名 94 | public let path:String // 文件路径 95 | public let content:String // 文件内容 96 | } 97 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/TestOC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestOC.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/11/18. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class TestOC: Test { 12 | 13 | public func testWorkspace() { 14 | 15 | let allNodes = ParseOC.ocNodes(workspacePath: Config.workPath()) 16 | 17 | var saveStr = "" 18 | for aNode in allNodes { 19 | saveStr += "//\(aNode.identifier)\n---\n\(aNode.source)\n\n" 20 | } 21 | FileHandle.writeToDownload(fileName: "methodSource", content: saveStr) 22 | 23 | } 24 | 25 | public func parseOC(groups:[[String]],index:Int) { 26 | 27 | guard index < groups.count else { 28 | return 29 | } 30 | 31 | } 32 | 33 | public static func testM(filePath:String) { 34 | let mContent = FileHandle.fileContent(path: filePath) 35 | let node = ParseOCNodes(input: mContent, filePath: filePath).parse() 36 | var saveStr = "" 37 | for aNode in node.subNodes { 38 | saveStr += "//\(aNode.identifier)\n---\n\(aNode.source)\n\n" 39 | } 40 | FileHandle.writeToDownload(fileName: "oneFileMethodSource", content: saveStr) 41 | } 42 | 43 | public static func testUnUsedClass(filePath:String) { 44 | let allNodes = ParseOC.ocNodes(workspacePath: Config.workPath()) 45 | // var classSet:Set = ["NSObject"] 46 | var classSet:Set = Set() 47 | for aNode in allNodes { 48 | // 49 | if aNode.type == .class { 50 | let classValue = aNode.value as! OCNodeClass 51 | classSet.insert(classValue.className) 52 | } 53 | } 54 | // for a in classSet { 55 | // print(a) 56 | // } 57 | var allUsedClassSet:Set = Set() 58 | for aNode in allNodes { 59 | if aNode.type == .method { 60 | // 61 | let usedSet = ParseOCMethodContent.parseAMethodUsedClass(node: aNode, allClass: classSet) 62 | if usedSet.count > 0 { 63 | for aSet in usedSet { 64 | allUsedClassSet.insert(aSet) 65 | } 66 | } 67 | } 68 | } 69 | var unUsedClass:Set = Set() 70 | for a in classSet { 71 | if !allUsedClassSet.contains(a) { 72 | unUsedClass.insert(a) 73 | } 74 | } 75 | for aSet in unUsedClass { 76 | print(aSet) 77 | } 78 | 79 | } 80 | 81 | // MARK:TODO:方法调用 82 | public static func testMethodCall(filePath:String) { 83 | 84 | let mContent = FileHandle.fileContent(path: filePath) 85 | let node = ParseOCNodes(input: mContent, filePath: filePath).parse() 86 | for aNode in node.subNodes { 87 | if aNode.type == .method { 88 | // 89 | } 90 | } 91 | } 92 | 93 | public static func testOC() { 94 | 95 | let ocPath = Bundle.main.path(forResource: "AppDelegate", ofType: "txt") ?? "" 96 | let ocContent = FileHandle.fileContent(path: ocPath) 97 | 98 | let node = ParseOCNodes(input: ocContent, filePath: ocPath).parse() 99 | 100 | print(node) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/Apply/FindWhoUseClassAndFunction.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FindWhoUseClassAndFunction.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2020/2/13. 6 | // Copyright © 2020 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct FindWhoUseClassAndFunction { 12 | 13 | static func whoUseClass(workSpacePath:String, className: String) { 14 | // 初始化类集合 15 | var classesSet = Set() 16 | classesSet.insert(className) 17 | 18 | let allNodes = ParseOC.ocNodes(workspacePath: workSpacePath) 19 | var allClassesSet = Set() 20 | 21 | var classAndBaseClasses = [String:String]() // 所有类和基类的关系 22 | // 找出基类 23 | for aNode in allNodes { 24 | if aNode.type == .class { 25 | let classValue = aNode.value as! OCNodeClass 26 | allClassesSet.insert(classValue.className) 27 | if classValue.baseClass.count > 0 { 28 | classAndBaseClasses[classValue.className] = classValue.baseClass 29 | } 30 | 31 | } 32 | } 33 | 34 | //MARK: 找继承了制定类的类 35 | for aNode in allNodes { 36 | if aNode.type == .class { 37 | let classValue = aNode.value as! OCNodeClass 38 | 39 | guard let nodeBaseClasses = classAndBaseClasses[classValue.className] else { 40 | continue 41 | } 42 | 43 | // 基类链里如果存在所制定的类名,则加入收集的集合里 44 | if nodeBaseClasses.contains(className) { 45 | classesSet.insert(classValue.className) 46 | } 47 | } 48 | } // end for 49 | 50 | //MARK: 看看哪些方法用了制定的类 51 | 52 | // 初始化调用了制定类的方法节点合集 53 | var classUsedInMethodsSet = Set() 54 | 55 | for aNode in allNodes { 56 | if aNode.type == .method { 57 | 58 | // 用过的类 59 | let usedSet = FindWhoUseClassAndFunction.parseAMethodUsedClass(node: aNode, allClass: allClassesSet) 60 | if usedSet.count > 0 { 61 | for aClass in classesSet { 62 | if usedSet.contains(aClass) { 63 | classUsedInMethodsSet.insert(aNode.identifier) 64 | } 65 | } 66 | } 67 | 68 | 69 | } // end if 70 | } // end for 71 | 72 | //MARK: 保存到 excel 里 73 | 74 | // 取出 owner 和 bundle 对应关系 75 | let classBundleOwner = OCApplyFeature.loadClassBundleOwner() 76 | 77 | var saveClassUsedMethodsAndBundlesStr = "方法,Bundle,Owner\n" 78 | for aMethod in classUsedInMethodsSet { 79 | let className = OCApplyFeature.bundleAndClassFromName(name: aMethod) 80 | guard let kClassBundleOwner = classBundleOwner[className] else { 81 | continue 82 | } 83 | let (bundle, owner) = kClassBundleOwner 84 | saveClassUsedMethodsAndBundlesStr.append("\(aMethod),\(bundle),\(owner)\n") 85 | } 86 | 87 | FileHandle.writeToDownload(fileName: "\(className)\(nowDateFormat())", content: saveClassUsedMethodsAndBundlesStr) 88 | 89 | 90 | } // end func 91 | 92 | 93 | 94 | //MARK: 找出单个方法内用过的类 95 | static func parseAMethodUsedClass(node: OCNode, allClass: Set) -> Set { 96 | var usedClassSet:Set = Set() 97 | guard node.type == .method else { 98 | return usedClassSet 99 | } 100 | 101 | let methodValue:OCNodeMethod = node.value as! OCNodeMethod 102 | for aNode in methodValue.tokenNodes { 103 | if allClass.contains(aNode.value) { 104 | usedClassSet.insert(aNode.value) 105 | } 106 | } 107 | 108 | return usedClassSet 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Data/AppDelegate.txt: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // GCDFetchFeed 4 | // 5 | // Created by DaiMing on 16/1/19. 6 | // Copyright © 2016年 Starming. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "SMRootViewController.h" 12 | #import "SMFeedListViewController.h" 13 | #import "SMMapViewController.h" 14 | #import "SMStyle.h" 15 | #import "SMFeedModel.h" 16 | #import "SMLagMonitor.h" 17 | #import "SMBarrierMonitor.h" 18 | 19 | #import 20 | 21 | @protocol SMTraceProtocol 22 | 23 | + (id)startTrace; 24 | 25 | - (void)getTraceId:(NSString *)key complete:(completeBlock)block; 26 | 27 | @end 28 | 29 | @interface AppDelegate () 30 | 31 | @end 32 | 33 | @implementation AppDelegate 34 | 35 | @synthesize data = _data; 36 | 37 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 38 | 39 | [[[SMBarrierMonitor alloc] init] start]; 40 | //这里是做卡顿监测 41 | // [[SMLagMonitor shareInstance] beginMonitor]; 42 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 43 | //首页 44 | SMRootViewController *rootVC = [[SMRootViewController alloc] init]; 45 | // UINavigationController *homeNav = [self styleNavigationControllerWithRootController:rootVC]; 46 | UINavigationController *homeNav = [self styleNavigationControllerWithRootController:rootVC]; 47 | // UITabBarItem *homeTab = [[UITabBarItem alloc] initWithTitle:@"频道" image:nil tag:1]; 48 | // homeTab.titlePositionAdjustment = UIOffsetMake(0, -20); 49 | // homeNav.tabBarItem = homeTab; 50 | // 51 | // //列表 52 | // SMFeedModel *feedModel = [SMFeedModel new]; 53 | // feedModel.fid = 0; 54 | // SMFeedListViewController *feedListVC = [[SMFeedListViewController alloc] initWithFeedModel:feedModel]; 55 | // UINavigationController *listNav = [self styleNavigationControllerWithRootController:feedListVC]; 56 | // UITabBarItem *listTab = [[UITabBarItem alloc] initWithTitle:@"列表" image:nil tag:2]; 57 | // listTab.titlePositionAdjustment = UIOffsetMake(0, -18); 58 | // listNav.tabBarItem = listTab; 59 | // 60 | // //map 61 | // SMMapViewController *mapVC = [[SMMapViewController alloc] init]; 62 | // UINavigationController *mapNav = [self styleNavigationControllerWithRootController:mapVC]; 63 | // UITabBarItem *mapTab = [[UITabBarItem alloc] initWithTitle:@"地图" image:nil tag:2]; 64 | // mapTab.titlePositionAdjustment = UIOffsetMake(0, -18); 65 | // mapNav.tabBarItem = mapTab; 66 | // 67 | // UITabBarController *tabBarC = [[UITabBarController alloc]initWithNibName:nil bundle:nil]; 68 | // tabBarC.tabBar.tintColor = [SMStyle colorPaperBlack]; 69 | // tabBarC.tabBar.barTintColor = [SMStyle colorPaperDark]; 70 | // UIView *shaowLine = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(tabBarC.tabBar.frame), 0.5)]; 71 | // shaowLine.backgroundColor = [UIColor colorWithHexString:@"D8D7D3"]; 72 | // [tabBarC.tabBar addSubview:shaowLine]; 73 | // tabBarC.tabBar.shadowImage = [UIImage new]; 74 | // tabBarC.tabBar.clipsToBounds = YES; 75 | // tabBarC.viewControllers = @[homeNav,listNav,mapNav]; 76 | 77 | // self.window.rootViewController = tabBarC; 78 | 79 | 80 | 81 | self.window.rootViewController = homeNav; 82 | [self.window makeKeyAndVisible]; 83 | return YES; 84 | } 85 | 86 | - (UINavigationController *)styleNavigationControllerWithRootController:(UIViewController *)vc { 87 | UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc]; 88 | nav.navigationBar.tintColor = [SMStyle colorPaperBlack]; 89 | nav.navigationBar.barTintColor = [SMStyle colorPaperDark]; 90 | UIView *shaowLine = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(nav.navigationBar.frame), CGRectGetWidth(nav.navigationBar.frame), 0.5)]; 91 | shaowLine.backgroundColor = [UIColor colorWithHexString:@"D8D7D3"]; 92 | [nav.navigationBar addSubview:shaowLine]; 93 | nav.navigationBar.translucent = NO; 94 | return nav; 95 | } 96 | 97 | 98 | 99 | @end 100 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/MachO/ParseMachO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RunOtool.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2020/2/25. 6 | // Copyright © 2020 ming. All rights reserved. 7 | // 8 | 9 | 10 | 11 | import Foundation 12 | 13 | struct ParseMachO { 14 | static let otool = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool" 15 | static let nm = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/nm" 16 | 17 | static func run() { 18 | print(terminalCommand(path: otool, args: ["-lV",Config.macho.rawValue])) 19 | 20 | } 21 | 22 | // MARK: 所有类 23 | // __DATA __objc_classlist 24 | static func classes() -> String { 25 | return terminalCommand(path: otool, args: ["-oV",Config.macho.rawValue]) 26 | } 27 | 28 | // MARK: 使用到的方法 29 | // __DATA __objc_selrefs 30 | static func sel() -> String { 31 | return terminalCommand(path: otool, args: ["-v","-s","__DATA","__objc_selrefs",Config.macho.rawValue]) 32 | } 33 | 34 | // MARK: load commands 35 | static func loadCommands() -> String { 36 | return terminalCommand(path: otool, args: ["-lV",Config.macho.rawValue]) 37 | } 38 | 39 | // MARK: Mach header 40 | static func machHeader() -> String { 41 | return terminalCommand(path: otool, args: ["-hV",Config.macho.rawValue]) 42 | } 43 | 44 | // MARK: 共享库 shared libraries 45 | static func sharedLibraries() -> String { 46 | return terminalCommand(path: otool, args: ["L",Config.macho.rawValue]) 47 | } 48 | 49 | // MARK: 汇编 50 | // print the text section 51 | static func assemble() -> String { 52 | return terminalCommand(path: otool, args: ["-t","-v",Config.macho.rawValue]) 53 | } 54 | 55 | // MARK: 汇编 56 | // print all text sections 57 | static func assembleAllSections() -> String { 58 | return terminalCommand(path: otool, args: ["-x","-v",Config.macho.rawValue]) 59 | } 60 | 61 | // MARK: 显示程序符号表 62 | static func symbol() -> String { 63 | return terminalCommand(path: nm, args: ["-g",Config.macho.rawValue]) 64 | } 65 | 66 | /* otool 命令参数说明 67 | -f print the fat headers 68 | -a print the archive header 69 | -h print the mach header 70 | -l print the load commands 71 | -L print shared libraries used 72 | -D print shared library id name 73 | -t print the text section (disassemble with -v) 74 | -x print all text sections (disassemble with -v) 75 | -p start dissassemble from routine name 76 | -s print contents of section 77 | -d print the data section 78 | -o print the Objective-C segment 79 | -r print the relocation entries 80 | -S print the table of contents of a library (obsolete) 81 | -T print the table of contents of a dynamic shared library (obsolete) 82 | -M print the module table of a dynamic shared library (obsolete) 83 | -R print the reference table of a dynamic shared library (obsolete) 84 | -I print the indirect symbol table 85 | -H print the two-level hints table (obsolete) 86 | -G print the data in code table 87 | -v print verbosely (symbolically) when possible 88 | -V print disassembled operands symbolically 89 | -c print argument strings of a core file 90 | -X print no leading addresses or headers 91 | -m don't use archive(member) syntax 92 | -B force Thumb disassembly (ARM objects only) 93 | -q use llvm's disassembler (the default) 94 | -Q use otool(1)'s disassembler 95 | -mcpu=arg use `arg' as the cpu for disassembly 96 | -j print opcode bytes 97 | -P print the info plist section as strings 98 | -C print linker optimization hints 99 | */ 100 | 101 | // 执行命令行 102 | static func terminalCommand(path:String, args:[String]) -> String { 103 | let pipe = Pipe() 104 | let file = pipe.fileHandleForReading 105 | 106 | let task = Process() 107 | task.launchPath = path 108 | task.arguments = args 109 | task.standardOutput = pipe 110 | task.launch() 111 | 112 | let data = file.readDataToEndOfFile() 113 | return String(data: data, encoding: String.Encoding.utf8) ?? "" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/ModifyOCContent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModifyOCContent.swift 3 | // MethodTraceAnalyze 4 | // 5 | // Created by ming on 2020/1/3. 6 | // Copyright © 2020 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct commentedOutUnUsedClassStruct { 12 | var filePath: String 13 | var lineStart: Int 14 | var lineEnd: Int 15 | var lineCount: Int // 有多少行 16 | var source: String // 代码 17 | var className: String // 类名 18 | var classIdentifier: String // 编号, 19 | } 20 | 21 | public class ModifyOCContent { 22 | private var filePath: String 23 | private var lineContent: [String] 24 | private var tokenNodes: [OCTokenNode] 25 | 26 | public init(inputFilePath: String) { 27 | filePath = inputFilePath 28 | let content = FileHandle.fileContent(path: inputFilePath) 29 | let formatInput = content.replacingOccurrences(of: "\r\n", with: "\n") 30 | lineContent = formatInput.components(separatedBy: .newlines) 31 | tokenNodes = ParseOCTokens(input: formatInput).parse() 32 | } 33 | 34 | // 注释掉无用的类 35 | public func commentedOutUnUsedClass(unUsedClasses:Set) -> [commentedOutUnUsedClassStruct] { 36 | enum State:String { 37 | case normal 38 | case at 39 | case atInterface 40 | case atImplementation 41 | case atProtocol 42 | 43 | case atContent // 在 @ + 名字后到 @end 之间的内容 44 | } 45 | 46 | var reClasses = [commentedOutUnUsedClassStruct]() 47 | 48 | var currentState:State = .normal 49 | var currentLineStart = 0 50 | var currentClassName = "" 51 | var currentClassIdentifier = "" 52 | 53 | var index = 0 54 | for aNode in tokenNodes { 55 | index += 1 56 | if currentState == .atContent { 57 | if index < tokenNodes.count { 58 | let nextNode = tokenNodes[index] 59 | if "\(aNode.value)\(nextNode.value)" == "@end" { 60 | // 61 | var sourceContent = "" 62 | for i in currentLineStart.. [String:[String]] { 33 | var chainMap = [String:[String]]() 34 | 35 | func recusiveParent(container:[String], parent:String) -> [String] { 36 | // 三种情况可能会跳不出 37 | if classAndBaseClassMap.keys.contains(parent) && parent != "NSObject" && classAndBaseClassMap[parent] != parent { 38 | let grandpa = classAndBaseClassMap[parent] ?? "" 39 | var mContainer = container 40 | mContainer.append(grandpa) 41 | return recusiveParent(container: mContainer, parent: grandpa) 42 | } 43 | 44 | return container 45 | } 46 | 47 | for (k,v) in classAndBaseClassMap { 48 | var vArr = [String]() 49 | vArr.append(v) 50 | chainMap[k] = recusiveParent(container: vArr, parent: v) 51 | 52 | } 53 | return chainMap 54 | } 55 | 56 | // MARK: 进行中的统计 57 | 58 | // 有哪些运行时方法调用,没有源码 59 | static var noSourceMethod = [String]() 60 | static func noSourceMethod(methodId:String) { 61 | runSerial { 62 | noSourceMethod.append(methodId) 63 | } 64 | } 65 | 66 | // 哪些方法是直接用的父类的方法 67 | static var useBaseClassMethod = [String]() 68 | static func useBaseClassMethod(methodId:String) { 69 | runSerial { 70 | useBaseClassMethod.append(methodId) 71 | } 72 | 73 | } 74 | 75 | // 方法 76 | static var methodContent = [String:String]() 77 | static func methodContent(method:String, content:String) { 78 | runSerial { 79 | methodContent[method] = content 80 | } 81 | } 82 | 83 | // 类和基类对应表 84 | static var classAndBaseClass = [String:String]() 85 | static func classAndBaseClass(aClass: String, baseClass: String) { 86 | runSerial { 87 | classAndBaseClass[aClass] = baseClass 88 | // print("aClass:\(aClass) baseClass:\(baseClass)") 89 | } 90 | } 91 | 92 | // 每个文件的行数 93 | static var fileLines = [String:Int]() 94 | static var fileLinesTotal = 0 95 | static func fileLine(filePath:String, lines:Int) { 96 | runSerial { 97 | fileLines[filePath] = lines 98 | fileLinesTotal += lines 99 | // print("filePath: \(filePath) lines: \(lines)") 100 | print("lines total: \(fileLinesTotal)") 101 | } 102 | } 103 | 104 | // 没有使用 group path 的文件 105 | static var noUseGroupPathes = [String]() 106 | static func noUseGroupPath(fileName:String) { 107 | runSerial { 108 | noUseGroupPathes.append(fileName) 109 | // print("no use group path:\(fileName)") 110 | } 111 | } 112 | 113 | // MARK: 基本工具 114 | typealias WorkQueueClosure = ()->Void 115 | static let workQueue = DispatchQueue(label: "com.MethodTraceAnalyze.statisticsQueue",qos: DispatchQoS.background) 116 | static func runSerial(closure:@escaping WorkQueueClosure) { 117 | workQueue.async(execute: closure) 118 | } 119 | 120 | static func showAll() { 121 | 122 | var saveStr = "" 123 | saveStr.append("无用类有: \(OCStatistics.unUsedClasses.count)\n") 124 | saveStr.append("方法总数: \(OCStatistics.methodContent.keys.count)\n") 125 | saveStr.append("行总数: \(OCStatistics.fileLinesTotal)\n") 126 | 127 | // 按行排序 128 | let sortFileLines = fileLines.sortedByValue 129 | saveStr.append("文件行数排序:") 130 | for (k,v) in sortFileLines { 131 | saveStr.append("\(k) \(v)\n") 132 | 133 | } 134 | print(saveStr) 135 | FileHandle.writeToDownload(fileName: "Statistics\(nowDateFormat())", content: saveStr) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/XML/ParseStandXML.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseStandXML.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/8/28. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct XMLNode { 12 | public let name: String 13 | public let attributes: [XMLTagAttribute] 14 | public var value: String 15 | public var subNodes: [XMLNode] 16 | } 17 | 18 | public class ParseStandXML { 19 | 20 | private var tagNodes: [XMLTagNode] 21 | private var nodeTree: XMLNode 22 | 23 | enum state { 24 | case normal 25 | case start 26 | case value 27 | case end 28 | } 29 | 30 | public init(input: String) { 31 | tagNodes = ParseStandXMLTags(input: input).parse() 32 | //ParseStandXMLTags.des(tagNodes: tagNodes) 33 | nodeTree = XMLNode(name: "root", attributes: [XMLTagAttribute](), value: "", subNodes: [XMLNode]()) 34 | 35 | } 36 | 37 | public func parse() -> XMLNode { 38 | nodeTree = recusiveParseTagNodes(parentNode: XMLNode(name: "root", attributes: [XMLTagAttribute](), value: "", subNodes: [XMLNode]()), tagNodes: tagNodes) 39 | return nodeTree 40 | } // end func 41 | 42 | public func recusiveParseTagNodes(parentNode: XMLNode, tagNodes: [XMLTagNode]) -> XMLNode { 43 | var pNode = parentNode 44 | var currentState:state = .normal 45 | var tagNodeArrs = [[XMLTagNode]]() // 一级 array 记录一组 46 | var currentTagNodeArr = [XMLTagNode]() // 二级 array 47 | var currentTagName = "" 48 | for node in tagNodes { 49 | if (node.type == .xml || node.type == .single) && currentState != .start { 50 | currentState = .normal 51 | currentTagNodeArr.append(node) 52 | //添加到一级 53 | tagNodeArrs.append(currentTagNodeArr) 54 | currentTagNodeArr = [XMLTagNode]() 55 | continue 56 | } 57 | // 以下顺序不可变 58 | // 当遇到.end 类型时将一组 XMLTagNode 加到 tagNodeArrs 里。然后重置。 59 | if node.type == .end && node.name == currentTagName { 60 | currentState = .end 61 | currentTagNodeArr.append(node) 62 | // 添加到一级 63 | tagNodeArrs.append(currentTagNodeArr) 64 | // 重置 65 | currentTagNodeArr = [XMLTagNode]() 66 | currentTagName = "" 67 | continue 68 | } 69 | if currentState == .start { 70 | currentTagNodeArr.append(node) 71 | continue 72 | } 73 | if node.type == .start { 74 | currentState = .start 75 | currentTagNodeArr.append(node) 76 | currentTagName = node.name 77 | continue 78 | } 79 | 80 | } // end for 81 | 82 | for tagNodeArr in tagNodeArrs { 83 | if tagNodeArr.count == 1 { 84 | // 只有一个的情况,即 xml 和 single 85 | let aTagNode = tagNodeArr[0] 86 | pNode.subNodes.append(tagNodeToNode(tagNode: aTagNode)) 87 | } else if tagNodeArr.count == 2 { 88 | // 2个的情况,就是比如

89 | let aTagNode = tagNodeArr[0] // 取 start 的信息 90 | pNode.subNodes.append(tagNodeToNode(tagNode: aTagNode)) 91 | } else if tagNodeArr.count > 2 { 92 | // 大于2个的情况 93 | let startTagNode = tagNodeArr[0] 94 | var startNode = tagNodeToNode(tagNode: startTagNode) 95 | let secondTagNode = tagNodeArr[1] 96 | 97 | // 判断是否是 value 这种情况比如

paragraph

98 | if secondTagNode.type == .value { 99 | // 有 value 的处理 100 | startNode.value = secondTagNode.value.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) 101 | pNode.subNodes.append(startNode) 102 | } else { 103 | // 有子标签的情况 104 | // 递归得到结果 105 | var newTagNodeArr = tagNodeArr 106 | newTagNodeArr.remove(at: tagNodeArr.count - 1) 107 | newTagNodeArr.remove(at: 0) 108 | 109 | pNode.subNodes.append(recusiveParseTagNodes(parentNode: startNode, tagNodes: newTagNodeArr)) 110 | } // end else 111 | 112 | } // end else if 113 | 114 | } // end for 115 | 116 | 117 | return pNode 118 | } 119 | 120 | private func tagNodeToNode(tagNode: XMLTagNode) -> XMLNode { 121 | return XMLNode(name: tagNode.name, attributes: tagNode.attributes, value: tagNode.value, subNodes: [XMLNode]()) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/XML/ParseStandXMLTagTokens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseStandXML.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/8/27. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum XMLTagTokensType { 12 | case tag 13 | case value 14 | } 15 | 16 | public struct XMLTagTokens { 17 | public let type: XMLTagTokensType 18 | public let tokens: [Token] 19 | } 20 | 21 | public class ParseStandXMLTagTokens { 22 | 23 | private enum State { 24 | case normal 25 | case startTag 26 | case cdata 27 | } 28 | 29 | private var tokens: [Token] 30 | private var allTagTokens: [XMLTagTokens] 31 | 32 | private var currentIndex: Int 33 | private var currentToken: Token 34 | private var currentState: State 35 | private var currentTokens: [Token] 36 | 37 | public init(input: String) { 38 | tokens = Lexer(input: input, type: .plain).allTkFast(operaters: "<>=\"/?![]") 39 | 40 | allTagTokens = [XMLTagTokens]() // 第一步 Token 整理按照 tag、value 类型整理 41 | 42 | currentIndex = 0 43 | currentToken = tokens[currentIndex] 44 | currentState = .normal 45 | currentTokens = [Token]() 46 | } 47 | 48 | // 解析 driver 49 | public func parse() -> [XMLTagTokens] { 50 | parseNext() 51 | while currentToken != .eof { 52 | parseNext() 53 | } 54 | return allTagTokens 55 | } 56 | 57 | // 调试打印结果用 58 | static func des(allTagTokens: [XMLTagTokens]) { 59 | for tks in allTagTokens { 60 | print(tks) 61 | for tk in tks.tokens { 62 | print(tk) 63 | } 64 | print("\n") 65 | } 66 | } 67 | 68 | private func parseNext() { 69 | if currentToken == .newLine { 70 | advanceTk() 71 | return 72 | } 73 | // 处理 cdata 的情况 74 | if currentState == .cdata { 75 | if currentToken == .id("]") && peekTk() == .id("]") && peekTkStep(step: 2) == .id(">") { 76 | if currentTokens.count > 0 { 77 | addTagTokens(type: .value) // 结束一组 78 | } 79 | currentState = .normal 80 | advanceTk() // jump ] 81 | advanceTk() // jump ] 82 | advanceTk() // jump > 83 | return 84 | } 85 | currentTokens.append(currentToken) 86 | advanceTk() 87 | return 88 | } 89 | 90 | // tag 的值 value 91 | if currentState == .normal && currentToken != .id("<") { 92 | currentTokens.append(currentToken) 93 | advanceTk() 94 | return 95 | } 96 | 97 | // 111 | if currentTokens.count > 0 { 112 | addTagTokens(type: .value) // 结束一组 113 | } 114 | currentState = .startTag 115 | advanceTk() 116 | return 117 | } 118 | 119 | if currentState == .startTag && currentToken != .id(">") { 120 | currentTokens.append(currentToken) 121 | advanceTk() 122 | return 123 | } 124 | 125 | // 126 | if currentState == .startTag && currentToken == .id(">") { 127 | currentState = .normal 128 | addTagTokens(type: .tag) // 结束一组 129 | advanceTk() 130 | return 131 | } 132 | } 133 | 134 | 135 | private func addTagTokens(type: XMLTagTokensType) { 136 | var isValid = false 137 | for tk in currentTokens { 138 | if tk == .space { 139 | 140 | } else { 141 | isValid = true 142 | } 143 | } 144 | if isValid { 145 | allTagTokens.append(XMLTagTokens(type: type, tokens: currentTokens)) 146 | } 147 | 148 | currentTokens = [Token]() 149 | } 150 | 151 | 152 | // MARK: 辅助 153 | private func peekTk() -> Token? { 154 | return peekTkStep(step: 1) 155 | } 156 | 157 | private func peekTkStep(step: Int) -> Token? { 158 | let peekIndex = currentIndex + step 159 | guard peekIndex < tokens.count else { 160 | return nil 161 | } 162 | return tokens[peekIndex] 163 | } 164 | 165 | private func advanceTk() { 166 | currentIndex += 1 167 | guard currentIndex < tokens.count else { 168 | return 169 | } 170 | currentToken = tokens[currentIndex] 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Xcodeproj/ParseXcodeprojTreeNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseXcodeprojTreeNode.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/9/6. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // TODO: 协议支持 12 | public enum XcodeprojTreeNodeType { 13 | case value 14 | case keyValue 15 | case array 16 | } 17 | 18 | public struct XcodeprojTreeNodeKey { 19 | public var name: String 20 | public var comment: String 21 | } 22 | 23 | public struct XcodeprojTreeNodeArrayValue { 24 | public var name: String 25 | public var comment: String 26 | } 27 | 28 | public struct XcodeprojTreeNodeKv { 29 | public var key: XcodeprojTreeNodeKey 30 | public var value: XcodeprojTreeNode 31 | } 32 | 33 | public struct XcodeprojTreeNode { 34 | public var type: XcodeprojTreeNodeType 35 | public var value: String 36 | public var comment: String 37 | public var kvs: [XcodeprojTreeNodeKv] 38 | public var arr: [XcodeprojTreeNodeArrayValue] 39 | } 40 | 41 | public class ParseXcodeprojTreeNode { 42 | private var allNodes: [XcodeprojNode] 43 | 44 | // 第二轮递归 45 | private enum RState { 46 | case normal 47 | case startDicRecusive 48 | case startArr 49 | 50 | } 51 | 52 | public init(input: String) { 53 | allNodes = ParseXcodeprojNode(input: input).parse() 54 | } 55 | 56 | public func parse() -> XcodeprojTreeNode { 57 | //ParseXcodeprojNode.des(nodes: nodes) 58 | let rootNode = recusiveParseNodes(parentNode: defaultXcodeprojTreeNode(), nodes: allNodes) 59 | 60 | return rootNode 61 | } 62 | 63 | // 获取一个默认的 treenode 64 | private func defaultXcodeprojTreeNode() -> XcodeprojTreeNode { 65 | return XcodeprojTreeNode(type: .keyValue, value: "", comment: "", kvs: [XcodeprojTreeNodeKv](), arr: [XcodeprojTreeNodeArrayValue]()) 66 | } 67 | 68 | 69 | public func recusiveParseNodes(parentNode: XcodeprojTreeNode, nodes: [XcodeprojNode]) -> XcodeprojTreeNode { 70 | var pNode = parentNode 71 | var currentState:RState = .normal 72 | var currentLevel = 0 73 | 74 | var recusiveNodeArr = [XcodeprojNode]() 75 | 76 | var currentTreeNodeType:XcodeprojTreeNodeType = .value 77 | var currentKey = XcodeprojTreeNodeKey(name: "", comment: "") 78 | var currentValue = defaultXcodeprojTreeNode() 79 | var currentArr = [XcodeprojTreeNodeArrayValue]() 80 | 81 | // 重置 key value 的解析 82 | func resetKvParseCurrent() { 83 | currentTreeNodeType = .value 84 | currentKey = XcodeprojTreeNodeKey(name: "", comment: "") 85 | currentValue = defaultXcodeprojTreeNode() 86 | } 87 | 88 | 89 | // 添加一个 key value 的 node 90 | func appendKvNode() { 91 | let kv = XcodeprojTreeNodeKv(key: currentKey, value: currentValue) 92 | pNode.kvs.append(kv) 93 | resetKvParseCurrent() 94 | } 95 | 96 | 97 | for node in nodes { 98 | // 如果是 Dic 的情况 99 | if node.type == .dicStart { 100 | currentLevel += 1 101 | if currentLevel > 1 { 102 | currentState = .startDicRecusive 103 | } 104 | 105 | } 106 | 107 | if node.type == .dicEnd { 108 | currentLevel -= 1 109 | if currentLevel == 1 { 110 | currentState = .normal 111 | recusiveNodeArr.append(node) 112 | // 下一级 dic 的 node 收集完毕,开始递归获取 value node 的 113 | currentValue = recusiveParseNodes(parentNode: defaultXcodeprojTreeNode(), nodes: recusiveNodeArr) 114 | appendKvNode() 115 | recusiveNodeArr = [XcodeprojNode]() 116 | continue 117 | } 118 | 119 | } 120 | 121 | // 以下顺序不可改 122 | // 收集递归所需 node 集 123 | if currentState == .startDicRecusive { 124 | recusiveNodeArr.append(node) 125 | continue 126 | } 127 | 128 | // 如果是 Arr 的情况 129 | if node.type == .arrStart { 130 | currentState = .startArr 131 | continue 132 | } 133 | 134 | if node.type == .arrValue && currentState == .startArr { 135 | let arrValue = XcodeprojTreeNodeArrayValue(name: node.value, comment: node.codeComment) 136 | currentArr.append(arrValue) 137 | continue 138 | } 139 | 140 | if node.type == .arrEnd { 141 | currentState = .normal 142 | pNode.kvs.append(XcodeprojTreeNodeKv(key: currentKey, value: XcodeprojTreeNode(type: .array, value: "", comment: "", kvs: [XcodeprojTreeNodeKv](), arr: currentArr))) 143 | currentArr = [XcodeprojTreeNodeArrayValue]() 144 | continue 145 | } 146 | 147 | // key value 的记录 148 | if node.type == .dicKey { 149 | currentKey.name = node.value 150 | currentKey.comment = node.codeComment 151 | pNode.type = .keyValue 152 | continue 153 | } 154 | if node.type == .dicValue { 155 | currentValue.value = node.value 156 | currentValue.comment = node.codeComment 157 | appendKvNode() 158 | continue 159 | } // end if 160 | 161 | } // end for 162 | 163 | return pNode 164 | } // end fun recusiveParseNodes 165 | 166 | 167 | } 168 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/JSON/ParseJSONTokens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseJSON.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/10/25. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum JSONTokenType { 12 | case startDic // { 13 | case endDic // } 14 | case startArray // [ 15 | case endArray // ] 16 | case key // key 17 | case value // value 18 | } 19 | 20 | public struct JSONToken { 21 | public let type: JSONTokenType 22 | public let value: String 23 | } 24 | 25 | public class ParseJSONTokens { 26 | 27 | private enum State { 28 | case normal 29 | case keyStart 30 | case valueStart 31 | } 32 | 33 | private var tokens:[Token] 34 | private var allJSONTokens: [JSONToken] 35 | 36 | private var currentIndex: Int 37 | private var currentToken: Token 38 | private var currentState: State 39 | private var currentValue: String 40 | 41 | public init(input: String) { 42 | tokens = Lexer(input: input, type: .plain).allTkFastWithoutNewLineAndWhitespace(operaters: "{}[]\":,") 43 | 44 | allJSONTokens = [JSONToken]() 45 | 46 | currentIndex = 0 47 | currentToken = tokens[currentIndex] 48 | currentState = .normal 49 | currentValue = "" 50 | } 51 | 52 | 53 | // 解析 54 | public func parse() -> [JSONToken] { 55 | parseNext() 56 | while currentToken != .eof { 57 | parseNext() 58 | } 59 | return allJSONTokens 60 | } 61 | 62 | private func parseNext() { 63 | // key 64 | if currentState == .keyStart { 65 | if currentToken == .id("\"") { 66 | addToken(type: .key) 67 | currentState = .normal 68 | } else { 69 | currentValue.append(currentToken.des()) 70 | } 71 | advanceTk() 72 | return 73 | } // end if currentState == .keyStart 74 | 75 | // value 76 | if currentState == .valueStart { 77 | if currentToken == .id("\"") { 78 | addToken(type: .value) 79 | currentState = .normal 80 | } else { 81 | currentValue.append(currentToken.des()) 82 | } 83 | advanceTk() 84 | return 85 | } // end if currentState == .valueStart 86 | 87 | if currentState == .normal { 88 | // 当遇到:符号后面是" value 开始 89 | if currentToken == .id(":") { 90 | if peekTk() == .id("\"") { 91 | currentState = .valueStart 92 | advanceTk() 93 | advanceTk() 94 | return 95 | } 96 | 97 | if peekTk() == .id("{") || peekTk() == .id("[") { 98 | advanceTk() 99 | return 100 | } 101 | 102 | // 如果:后不是接{或者[,一般就是非字符串的数字,可以直接添加到 token 里 103 | advanceTk() 104 | currentValue.append(currentToken.des()) 105 | addToken(type: .value) 106 | advanceTk() 107 | return 108 | 109 | 110 | } 111 | // 当遇到,符号 key 开始 112 | if currentToken == .id(",") && peekTk() == .id("\"") { 113 | currentState = .keyStart 114 | advanceTk() 115 | advanceTk() 116 | return 117 | } 118 | if currentToken == .id(",") { 119 | advanceTk() 120 | return 121 | } 122 | if currentToken == .id("{") { 123 | addToken(type: .startDic) 124 | if peekTk() == .id("\"") { 125 | // 当遇到 { 符号 key 开始 126 | currentState = .keyStart 127 | advanceTk() 128 | } 129 | advanceTk() 130 | return 131 | } 132 | if currentToken == .id("}") { 133 | addToken(type: .endDic) 134 | advanceTk() 135 | return 136 | } 137 | if currentToken == .id("[") { 138 | addToken(type: .startArray) 139 | if peekTk() == .id("\"") { 140 | // 遇到 [ key 141 | currentState = .valueStart 142 | advanceTk() 143 | } 144 | advanceTk() 145 | return 146 | } 147 | if currentToken == .id("]") { 148 | addToken(type: .endArray) 149 | advanceTk() 150 | return 151 | } 152 | // 默认作为值处理 153 | if currentValue.count == 0 { 154 | currentValue = currentToken.des() 155 | } 156 | addToken(type: .value) 157 | advanceTk() 158 | return 159 | 160 | } // end if currentState == .normal 161 | 162 | advanceTk() 163 | 164 | } 165 | 166 | private func addToken(type: JSONTokenType) { 167 | allJSONTokens.append(JSONToken(type:type,value:currentValue)) 168 | currentValue = "" 169 | } 170 | 171 | // MARK: 辅助 172 | private func peekTk() -> Token? { 173 | return peekTkStep(step: 1) 174 | } 175 | 176 | private func peekTkStep(step: Int) -> Token? { 177 | let peekIndex = currentIndex + step 178 | guard peekIndex < tokens.count else { 179 | return nil 180 | } 181 | return tokens[peekIndex] 182 | } 183 | 184 | private func advanceTk() { 185 | currentIndex += 1 186 | guard currentIndex < tokens.count else { 187 | return 188 | } 189 | currentToken = tokens[currentIndex] 190 | } 191 | } 192 | 193 | 194 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/JSON/ParseJSONItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseJSONItem.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/11/4. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum JSONItemType { 12 | case keyValue 13 | case value 14 | case array 15 | } 16 | 17 | public struct JSONItemKv { 18 | public var key: String 19 | public var value: JSONItem 20 | } 21 | 22 | public struct JSONItem { 23 | public var type: JSONItemType 24 | public var value: String 25 | public var kvs: [JSONItemKv] 26 | public var array: [JSONItem] 27 | } 28 | 29 | public class ParseJSONItem { 30 | private var tks: [JSONToken] 31 | 32 | 33 | 34 | public init(input:String) { 35 | tks = ParseJSONTokens(input: input).parse() 36 | 37 | } 38 | 39 | public func parse() -> JSONItem { 40 | let rootItem = recursiveTk(parentItem: defaultJSONItem(), Tks: tks) 41 | return rootItem 42 | } 43 | 44 | private func defaultJSONItem() -> JSONItem { 45 | return JSONItem(type: .value, value: "", kvs: [JSONItemKv](), array: [JSONItem]()) 46 | } 47 | 48 | public func recursiveTk(parentItem:JSONItem, Tks:[JSONToken]) -> JSONItem { 49 | enum rState { 50 | case normal 51 | case startDic 52 | case startArr 53 | case startKey 54 | } 55 | var pItem = parentItem 56 | var currentState:rState = .normal 57 | var currentDicLevel = 0 58 | var currentArrLevel = 0 59 | var recursiveTkArr = [JSONToken]() 60 | 61 | var currentItemType:JSONItemType = .value 62 | var currentKey = "" 63 | var currentValue = defaultJSONItem() 64 | var currentArr = [String]() 65 | 66 | func resetCurrent() { 67 | currentItemType = .value 68 | currentKey = "" 69 | currentValue = defaultJSONItem() 70 | } 71 | 72 | func appendKvItem() { 73 | let kv = JSONItemKv(key: currentKey, value: currentValue) 74 | pItem.kvs.append(kv) 75 | pItem.type = .keyValue 76 | resetCurrent() 77 | } 78 | 79 | func appendVItem() { 80 | pItem.type = .array 81 | pItem.array.append(currentValue) 82 | resetCurrent() 83 | } 84 | 85 | for tk in Tks { 86 | // 如果是字典情况 87 | if tk.type == .startDic && currentState != .startArr { 88 | currentDicLevel += 1 89 | currentState = .startDic 90 | if currentDicLevel == 1 { 91 | continue 92 | } 93 | 94 | } // end if tk.type == .startDic 95 | 96 | if tk.type == .endDic && currentState != .startArr { 97 | currentDicLevel -= 1 98 | if currentDicLevel == 0 { 99 | currentState = .normal 100 | // 将下一级收集完 101 | currentValue = recursiveTk(parentItem: defaultJSONItem(), Tks: recursiveTkArr) 102 | if currentKey.count > 0 { 103 | appendKvItem() 104 | } else { 105 | appendVItem() 106 | } 107 | 108 | recursiveTkArr = [JSONToken]() 109 | continue 110 | } 111 | } // if tk.type == .endDic 112 | 113 | if currentState == .startDic { 114 | recursiveTkArr.append(tk) 115 | continue 116 | } 117 | 118 | if tk.type == .startArray && currentState != .startDic { 119 | currentArrLevel += 1 120 | currentState = .startArr 121 | if currentArrLevel == 1 { 122 | continue 123 | } 124 | } 125 | 126 | if tk.type == .endArray && currentState != .startDic { 127 | currentArrLevel -= 1 128 | if currentArrLevel == 0 { 129 | currentState = .normal 130 | // 收集下一级 131 | currentValue = recursiveTk(parentItem: defaultJSONItem(), Tks: recursiveTkArr) 132 | if currentKey.count > 0 { 133 | appendKvItem() 134 | } else { 135 | appendVItem() 136 | } 137 | 138 | recursiveTkArr = [JSONToken]() 139 | continue 140 | } 141 | } // end if tk.type == .endArray 142 | 143 | // 下列顺序不可改 144 | // 收集递归所需 item 集 145 | if currentState == .startArr { 146 | recursiveTkArr.append(tk) 147 | continue 148 | } 149 | 150 | if currentState == .startKey { 151 | if tk.type == .value { 152 | currentValue = defaultJSONItem() 153 | currentValue.value = tk.value 154 | appendKvItem() 155 | currentState = .normal 156 | continue 157 | } 158 | } 159 | 160 | if tk.type == .value { 161 | currentValue = defaultJSONItem() 162 | currentValue.value = tk.value 163 | appendVItem() 164 | continue 165 | } 166 | 167 | // 如果是数组 arr 的情况 168 | if tk.type == .key { 169 | if pItem.type == .array { 170 | currentValue = defaultJSONItem() 171 | currentValue.value = tk.value 172 | appendVItem() 173 | continue 174 | } 175 | 176 | currentState = .startKey 177 | currentKey = tk.value 178 | continue 179 | } 180 | 181 | } // end for tk in Tks 182 | return pItem 183 | } 184 | 185 | } 186 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Xcodeproj/ParseXcodeprojSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseXcodeprojSource.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/9/19. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct XcodeprojSourceNode { 12 | let fatherValue: String // 文件夹 13 | let value: String // 文件的值 14 | let name: String // 文件名 15 | let type: String 16 | } 17 | 18 | // MARK: Main 19 | public class ParseXcodeprojSource { 20 | var proj: Xcodeproj 21 | var projPath: String 22 | 23 | public init(input: String) { 24 | let projectContent = FileHandle.fileContent(path: input) 25 | proj = ParseXcodeprojSection(input: projectContent).parse() 26 | projPath = "" 27 | let splitPathBySlashArr = input.components(separatedBy: "/") 28 | for v in splitPathBySlashArr { 29 | if v.hasSuffix("xcodeproj") { 30 | break 31 | } 32 | projPath.append("\(v)/") 33 | } 34 | 35 | } 36 | 37 | public func parseAllFiles() -> [String] { 38 | 39 | var nodes = [XcodeprojSourceNode]() 40 | 41 | // 第一次找出所有文件和文件夹 42 | for (k,v) in proj.pbxGroup { 43 | guard v.children.count > 0 else { 44 | continue 45 | } 46 | 47 | for child in v.children { 48 | // 如果满足条件表示是目录 49 | if proj.pbxGroup.keys.contains(child.value) { 50 | continue 51 | } 52 | // 满足条件是文件 53 | if proj.pbxFileReference.keys.contains(child.value) { 54 | guard let fileRefer = proj.pbxFileReference[child.value] else { 55 | continue 56 | } 57 | 58 | nodes.append(XcodeprojSourceNode(fatherValue: k, value: child.value, name: fileRefer.path, type: fileRefer.lastKnownFileType)) 59 | } 60 | } // end for children 61 | 62 | } // end for group 63 | 64 | // 通过遍历,然后递归每个 node 的 fatherValue 获得完整路径 65 | var fullPaths = [String]() 66 | // 白名单 67 | let suffixWhitelist = [".m",".mm",".h"] 68 | for node in nodes { 69 | let path = recusiveFatherPaths(node: node, path: node.name) 70 | // 处理 path 数据 71 | let pathArr = path.components(separatedBy: "/") 72 | var isAppend = false 73 | 74 | // 处理白名单 75 | for sw in suffixWhitelist { 76 | if path.hasSuffix(sw) { 77 | isAppend = true 78 | } 79 | } 80 | // 处理无效数据 81 | for v in pathArr { 82 | if v.hasPrefix("\"") { 83 | isAppend = false 84 | } 85 | } 86 | if isAppend { 87 | // 需要处理 ../ 往上级探的情况 88 | let projPathArr = projPath.components(separatedBy: "/") 89 | 90 | let pathArr = path.components(separatedBy: "/") 91 | var stepPath = "" 92 | var stepCount = 0 93 | var hasDoubleDot = false 94 | for pathItem in pathArr { 95 | if pathItem == ".." { 96 | stepCount += 1 97 | hasDoubleDot = true 98 | continue 99 | } 100 | if pathItem == "" { 101 | continue 102 | } 103 | stepPath.append("\(pathItem)/") 104 | } 105 | // 清理最后的/符号 106 | if stepPath.hasSuffix("/") { 107 | stepPath.removeLast() 108 | } 109 | 110 | // 根据 ../ 的数量往上级探 111 | var projNewPath = "" 112 | if hasDoubleDot { 113 | let cutCount = projPathArr.count - stepCount - 1 114 | var i = 0 115 | for pjItem in projPathArr { 116 | if i == cutCount { 117 | break 118 | } 119 | projNewPath.append("\(pjItem)/") 120 | i += 1 121 | } 122 | } else { 123 | projNewPath = projPath 124 | } 125 | // 拼接 126 | let combinePath = projNewPath + stepPath 127 | 128 | fullPaths.append(combinePath) 129 | } // end if 130 | 131 | } 132 | //print(fullPaths) 133 | return fullPaths 134 | } // end func 135 | 136 | // MARK:私有方法 137 | private func recusiveFatherPaths(node: XcodeprojSourceNode, path: String) -> String { 138 | let rootProj = proj.rootObject 139 | // 判断是否到了根上 140 | guard let pbxproj = proj.pbxProject[rootProj.value] else { 141 | return path 142 | } 143 | 144 | guard node.fatherValue != pbxproj.mainGroup else { 145 | return path 146 | } 147 | 148 | guard let fatherGroup = proj.pbxGroup[node.fatherValue] else { 149 | return path 150 | } 151 | 152 | var fatherPath = fatherGroup.path 153 | // source tree 不是 是 SOURCE_ROOT 时,会用到 name。这种情况不合理需要记录下来修改 154 | if fatherGroup.sourceTree != "" { 155 | OCStatistics.noUseGroupPath(fileName: path) 156 | if fatherGroup.name.count > 0 { 157 | fatherPath = fatherGroup.name 158 | } 159 | } 160 | 161 | 162 | // 拼路径 163 | var jointPath = path 164 | jointPath = "\(fatherPath)/\(path)" 165 | 166 | // 查找更上一级的路径 167 | for (k,v) in proj.pbxGroup { 168 | for child in v.children { 169 | if child.value == node.fatherValue { 170 | let rNode = XcodeprojSourceNode(fatherValue: k, value: child.value, name: "", type: "Folder") 171 | return recusiveFatherPaths(node: rNode, path: jointPath) 172 | } // end if 173 | } // end for children 174 | } // end for group 175 | 176 | return path 177 | 178 | } 179 | 180 | } 181 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Xcodeproj/ParseXcodeprojTokens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseXcodeproj.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/9/2. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum XcodeprojTokensType { 12 | case codeComment 13 | case string 14 | case id 15 | 16 | case leftBrace // { 17 | case rightBrace // } 18 | case leftParenthesis // ( 19 | case rightParenthesis // ) 20 | case equal // = 21 | case semicolon // ; 22 | case comma // , 23 | } 24 | 25 | // 一组 Token 26 | public struct XcodeprojTokens { 27 | public let type: XcodeprojTokensType 28 | public let tokens: [Token] 29 | } 30 | 31 | public class ParseXcodeprojTokens { 32 | 33 | private enum State { 34 | case normal 35 | case codeCommentStart 36 | case codeCommentStarStart 37 | case stringStart 38 | } 39 | 40 | private var inputTokens: [Token] 41 | private var allTokens: [XcodeprojTokens] 42 | 43 | // 基础属性 44 | private var currentIndex: Int 45 | private var currentToken: Token 46 | private var currentState: State 47 | private var currentTokens: [Token] 48 | 49 | 50 | public init(input: String) { 51 | inputTokens = Lexer(input: input, type: .plain).allTkFast(operaters: "/*={};\",()") 52 | allTokens = [XcodeprojTokens]() 53 | 54 | currentIndex = 0 55 | currentToken = inputTokens[currentIndex] 56 | currentState = .normal 57 | currentTokens = [Token]() 58 | } 59 | 60 | // 解析 61 | public func parse() -> [XcodeprojTokens] { 62 | parseNext() 63 | while currentToken != .eof { 64 | parseNext() 65 | } 66 | return allTokens 67 | } 68 | 69 | private func parseNext() { 70 | 71 | if currentState == .normal { 72 | if currentToken == .id("{") { 73 | addTokens(type: .leftBrace) 74 | advanceTk() 75 | return 76 | } 77 | if currentToken == .id("}") { 78 | addTokens(type: .rightBrace) 79 | advanceTk() 80 | return 81 | } 82 | if currentToken == .id("(") { 83 | addTokens(type: .leftParenthesis) 84 | advanceTk() 85 | return 86 | } 87 | if currentToken == .id(")") { 88 | addTokens(type: .rightParenthesis) 89 | advanceTk() 90 | return 91 | } 92 | if currentToken == .id("=") { 93 | addTokens(type: .equal) 94 | advanceTk() 95 | return 96 | } 97 | if currentToken == .id(";") { 98 | addTokens(type: .semicolon) 99 | advanceTk() 100 | return 101 | } 102 | if currentToken == .id(",") { 103 | addTokens(type: .comma) 104 | advanceTk() 105 | return 106 | } 107 | 108 | // 字符串 " 109 | if currentToken == .id("\"") { 110 | currentState = .stringStart 111 | advanceTk() 112 | return 113 | } 114 | // //这样的代码注释 115 | if currentToken == .id("/") && peekTk() == .id("/") { 116 | currentState = .codeCommentStart 117 | advanceTk() 118 | advanceTk() 119 | return 120 | } 121 | // /* 122 | if currentToken == .id("/") && peekTk() == .id("*") { 123 | currentState = .codeCommentStarStart 124 | advanceTk() 125 | advanceTk() 126 | return 127 | } 128 | if currentToken == .space { 129 | advanceTk() 130 | return 131 | } 132 | if currentToken == .newLine { 133 | advanceTk() 134 | return 135 | } 136 | 137 | currentTokens = [Token]() 138 | currentTokens.append(currentToken) 139 | addTokens(type: .id) 140 | advanceTk() 141 | return 142 | } 143 | if currentState == .stringStart { 144 | if currentToken == .id("\"") { 145 | addTokens(type: .string) 146 | currentState = .normal 147 | } else { 148 | currentTokens.append(currentToken) 149 | } 150 | advanceTk() 151 | return 152 | } 153 | 154 | if currentState == .codeCommentStart { 155 | if currentToken == .newLine { 156 | addTokens(type: .codeComment) 157 | currentState = .normal 158 | } else { 159 | currentTokens.append(currentToken) 160 | } 161 | advanceTk() 162 | return 163 | } 164 | 165 | if currentState == .codeCommentStarStart { 166 | if currentToken == .id("*") && peekTk() == .id("/") { 167 | addTokens(type: .codeComment) 168 | advanceTk() 169 | advanceTk() 170 | currentState = .normal 171 | return 172 | } else { 173 | currentTokens.append(currentToken) 174 | advanceTk() 175 | return 176 | } 177 | 178 | } 179 | 180 | advanceTk() 181 | 182 | } 183 | 184 | private func addTokens(type:XcodeprojTokensType) { 185 | allTokens.append(XcodeprojTokens(type: type, tokens: currentTokens)) 186 | currentTokens = [Token]() 187 | } 188 | 189 | // MARK: 辅助 190 | private func peekTk() -> Token? { 191 | return peekTkStep(step: 1) 192 | } 193 | 194 | private func peekTkStep(step: Int) -> Token? { 195 | let peekIndex = currentIndex + step 196 | guard peekIndex < inputTokens.count else { 197 | return nil 198 | } 199 | return inputTokens[peekIndex] 200 | } 201 | 202 | private func advanceTk() { 203 | currentIndex += 1 204 | guard currentIndex < inputTokens.count else { 205 | return 206 | } 207 | currentToken = inputTokens[currentIndex] 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Launch/ParseLaunchJSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseLaunchJSON.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/10/26. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public struct LaunchItem { 12 | public let name: String // 调用方法 13 | public var ph: String // B 代表开始、E 代表结束、BE 代表合并后的 Item、其它代表描述 14 | public var ts: String // 时间戳,开始时间 15 | public var cost: Int // 耗时 ms 16 | public var times: Int // 执行次数 17 | public var subItem: [LaunchItem] // 子 item 18 | public var parentItem:[LaunchItem] // 父 item 19 | } 20 | 21 | public class ParseLaunchJSON { 22 | 23 | private enum State { 24 | case normal 25 | case startName 26 | case startPh 27 | case startTs 28 | } 29 | 30 | private var tks: [JSONToken] 31 | private var allLaunchItem: [LaunchItem] 32 | private var state: State 33 | private var classBundle: [String:String] 34 | 35 | public init(input: String) { 36 | 37 | tks = ParseJSONTokens(input: input).parse() 38 | allLaunchItem = [LaunchItem]() 39 | state = .normal 40 | classBundle = [String:String]() 41 | 42 | } 43 | 44 | public func parse() -> [LaunchItem] { 45 | // 获取类和 bundle 的映射表 46 | classBundle = loadClassBundle() 47 | 48 | // 开始解析 49 | var currentName = "" 50 | var currentPh = "" 51 | var currentTs = "" 52 | 53 | func addItem() { 54 | allLaunchItem.append(LaunchItem(name: currentName, ph: currentPh, ts: currentTs, cost: 0, times: 1, subItem: [LaunchItem](), parentItem: [LaunchItem]())) 55 | currentName = "" 56 | currentPh = "" 57 | currentTs = "" 58 | state = .normal 59 | } 60 | 61 | for tk in tks { 62 | // 结束一个 item 63 | if tk.type == .endDic { 64 | addItem() 65 | continue 66 | } 67 | 68 | // key 69 | if tk.type == .key { 70 | if tk.value == "name" { 71 | state = .startName 72 | } 73 | if tk.value == "ph" { 74 | state = .startPh 75 | } 76 | if tk.value == "ts" { 77 | state = .startTs 78 | } 79 | 80 | continue 81 | } 82 | 83 | if tk.type == .value { 84 | if state == .startName { 85 | currentName = tk.value 86 | let s1 = currentName.components(separatedBy: "[")[1] 87 | let s2 = s1.components(separatedBy: "]")[0] 88 | 89 | currentName = "[\(classBundle[s2] ?? "other")]\(currentName)" 90 | state = .normal 91 | } 92 | if state == .startPh { 93 | currentPh = tk.value 94 | state = .normal 95 | } 96 | if state == .startTs { 97 | currentTs = tk.value 98 | state = .normal 99 | } 100 | continue 101 | } 102 | 103 | 104 | } 105 | 106 | return allLaunchItem 107 | } 108 | 109 | private func loadClassBundle() -> [String:String] { 110 | let path = Bundle.main.path(forResource: "ClassBundle1015", ofType: "csv") 111 | let oPath = path ?? "" 112 | 113 | let content = FileHandle.fileContent(path: oPath) 114 | 115 | let tokens = Lexer(input: content, type: .plain).allTkFast(operaters: ",") 116 | var allTks = [[Token]]() 117 | var currentTks = [Token]() 118 | for tk in tokens { 119 | if tk == .newLine { 120 | allTks.append(currentTks) 121 | currentTks = [Token]() 122 | continue 123 | } 124 | if tk == .id(",") { 125 | continue 126 | } 127 | currentTks.append(tk) 128 | } 129 | 130 | var classBundleDic = [String:String]() 131 | for tks in allTks { 132 | var i = 0 133 | var currentKey = "" 134 | for tk in tks { 135 | if i == 0 { 136 | currentKey = tk.des() 137 | } 138 | if i == 1 { 139 | classBundleDic[currentKey] = tk.des() 140 | } 141 | i += 1 142 | } 143 | } 144 | return classBundleDic 145 | } 146 | 147 | private func parseClassAndBundle() { 148 | let path = Bundle.main.path(forResource: "ClassAndBundle", ofType: "csv") 149 | let oPath = path ?? "" 150 | 151 | let content = FileHandle.fileContent(path: oPath) 152 | let contentWithoutWhiteSpace = content.replacingOccurrences(of: " ", with: "") 153 | let tokens = Lexer(input: contentWithoutWhiteSpace, type: .plain).allTkFast(operaters: ",") 154 | var allTks = [[Token]]() 155 | var currentTks = [Token]() 156 | for tk in tokens { 157 | if tk == .newLine { 158 | allTks.append(currentTks) 159 | currentTks = [Token]() 160 | continue 161 | } 162 | if tk == .id(",") { 163 | continue 164 | } 165 | currentTks.append(tk) 166 | } 167 | //print(allTks) 168 | allTks.remove(at: 0) 169 | var classBundleDic = [String:String]() 170 | for tks in allTks { 171 | var i = 0 172 | var currentKey = "" 173 | for tk in tks { 174 | if i == 2 { 175 | let className = tk.des() 176 | let classNameS1 = className.replacingOccurrences(of: "\"[\"\"", with: "") 177 | let classNameS2 = classNameS1.replacingOccurrences(of: "\"\"]\"", with: "") 178 | currentKey = classNameS2 179 | } 180 | if i == 4 { 181 | classBundleDic[currentKey] = tk.des() 182 | } 183 | i += 1 184 | } 185 | } 186 | var str = "" 187 | for (k,v) in classBundleDic { 188 | str.append("\(k),\(v)\n") 189 | } 190 | try! str.write(toFile: "\(Config.downloadPath.rawValue)classBundle1015.csv", atomically: true, encoding: String.Encoding.utf8) 191 | } 192 | 193 | } 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/XML/ParseStandXMLTags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseStandXMLTags.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/8/28. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum XMLTagNodeType { 12 | case xml 13 | case single // 单个标签 14 | case start // 开标签

15 | case value // 标签的值

value

16 | case end // 闭合的标签

17 | } 18 | 19 | public struct XMLTagAttribute { 20 | public let name: String 21 | public let value: String 22 | } 23 | 24 | public struct XMLTagNode { 25 | public let type: XMLTagNodeType 26 | public let value: String // 标签值 27 | public let name: String // 标签名 28 | public let attributes: [XMLTagAttribute] // 标签属性 29 | } 30 | 31 | public class ParseStandXMLTags { 32 | 33 | private var allTagTokens: [XMLTagTokens] 34 | private var allTagNodes: [XMLTagNode] 35 | 36 | private enum State { 37 | case startTagName 38 | case startAttributeName 39 | case startAttributeValue 40 | } 41 | 42 | public init(input: String) { 43 | allTagTokens = ParseStandXMLTagTokens(input: input).parse() 44 | allTagNodes = [XMLTagNode]() 45 | } 46 | 47 | public func parse() -> [XMLTagNode] { 48 | for tokens in allTagTokens { 49 | // 50 | parseTokens(tokens: tokens) 51 | } 52 | return allTagNodes 53 | } 54 | 55 | static func des(tagNodes: [XMLTagNode]) { 56 | for tagNode in tagNodes { 57 | print("name:\(tagNode.name)") 58 | print("type:\(tagNode.type)") 59 | print("value:\(tagNode.value)") 60 | print("attribute:\(tagNode.attributes)") 61 | print("\n") 62 | } 63 | } 64 | 65 | private func parseTokens(tokens: XMLTagTokens) { 66 | // 处理 tag 类型 67 | if tokens.type == .tag { 68 | enum pTagState { 69 | case start 70 | case questionMark 71 | case xml 72 | case tagName 73 | case attributeName 74 | case equal 75 | case attributeValue 76 | case startForwardSlash 77 | case endForwardSlash 78 | case startDoubleQuotationMarks 79 | case backSlash 80 | case endDoubleQuotationMarks 81 | } 82 | var state:pTagState = .start 83 | var attributes = [XMLTagAttribute]() 84 | var nodeName = "" 85 | var nodeType:XMLTagNodeType = .xml 86 | var currentXMLTagAttributeName = "" 87 | var currentXMLTagAttributeValue = "" 88 | 89 | for token in tokens.tokens { 90 | // 处理双引号字符串内的空格 91 | if state != .startDoubleQuotationMarks && token == .space { 92 | continue 93 | } 94 | 95 | // 正常处理 96 | if state == .start { 97 | if token.des() == "?" { 98 | state = .questionMark 99 | nodeType = .xml 100 | } else if token.des() == "/" { 101 | state = .startForwardSlash 102 | nodeType = .end 103 | } else { 104 | nodeName = token.des() 105 | nodeType = .start 106 | state = .tagName 107 | } 108 | continue 109 | } 110 | // / 111 | if state == .startForwardSlash { 112 | nodeName = token.des() 113 | continue 114 | } 115 | // ? 116 | if state == .questionMark { 117 | if token.des() == "xml" { 118 | state = .xml 119 | nodeName = "xml" 120 | } 121 | continue 122 | } 123 | // xml 124 | if state == .xml { 125 | currentXMLTagAttributeName = token.des() 126 | state = .attributeName 127 | continue 128 | } 129 | // 134 | if token.des() == "/" { 135 | nodeType = .single 136 | } 137 | continue 138 | } 139 | // attributeName = 140 | if state == .attributeName { 141 | if token.des() == "=" { 142 | state = .equal 143 | } 144 | continue 145 | } 146 | // = 147 | if state == .equal { 148 | if token.des() == "\"" { 149 | state = .startDoubleQuotationMarks 150 | } 151 | continue 152 | } 153 | // " 154 | if state == .startDoubleQuotationMarks { 155 | if token.des() == "\\" { 156 | state = .backSlash 157 | } else if token.des() == "\"" { 158 | // 添加属性 159 | state = .endDoubleQuotationMarks 160 | attributes.append(XMLTagAttribute(name: currentXMLTagAttributeName, value: currentXMLTagAttributeValue)) 161 | currentXMLTagAttributeName = "" 162 | currentXMLTagAttributeValue = "" 163 | } else { 164 | currentXMLTagAttributeValue.append(token.des()) 165 | } 166 | continue 167 | } 168 | // / 169 | if state == .backSlash { 170 | state = .startDoubleQuotationMarks 171 | continue 172 | } 173 | // " 174 | if state == .endDoubleQuotationMarks { 175 | if token.des() == "/" { 176 | state = .endForwardSlash 177 | nodeType = .single 178 | } else if token.des() == "?" { 179 | 180 | } else { 181 | state = .attributeName 182 | currentXMLTagAttributeName = token.des() 183 | } 184 | continue 185 | } // end if 186 | } // end for 187 | 188 | // 添加 Node 189 | allTagNodes.append(XMLTagNode(type: nodeType, value: "", name: nodeName, attributes: attributes)) 190 | } 191 | 192 | 193 | // 处理 value 类型 194 | if tokens.type == .value { 195 | 196 | var value = "" 197 | 198 | for token in tokens.tokens { 199 | value.append(token.des()) 200 | } 201 | allTagNodes.append(XMLTagNode(type: .value, value: value, name: "", attributes: [XMLTagAttribute]())) 202 | } 203 | 204 | } 205 | 206 | } 207 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Xcodeproj/ParseXcodeprojNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseXcodeprojNode.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/9/4. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum XcodeprojNodeType { 12 | case normal 13 | case root 14 | 15 | case dicStart // { 16 | case dicKey 17 | case dicValue 18 | case dicEnd // } 19 | 20 | case arrStart // ( 21 | case arrValue 22 | case arrEnd // ) 23 | } 24 | 25 | public struct XcodeprojNode { 26 | public let type: XcodeprojNodeType 27 | public let value: String 28 | public let codeComment: String 29 | public var subNodes: [XcodeprojNode] 30 | } 31 | 32 | public class ParseXcodeprojNode { 33 | private var allTokens: [XcodeprojTokens] 34 | private var allNodes: [XcodeprojNode] 35 | 36 | // 第一轮用 37 | private enum PState { 38 | case normal 39 | case startDicKey 40 | case startDicValue 41 | case startArrValue 42 | } 43 | 44 | 45 | public init(input: String) { 46 | allTokens = ParseXcodeprojTokens(input: input).parse() 47 | allNodes = [XcodeprojNode]() 48 | } 49 | 50 | public static func des(nodes: [XcodeprojNode]) { 51 | for node in nodes { 52 | print("type:\(node.type)") 53 | print("value:\(node.value)") 54 | print("codeComment:\(node.codeComment)") 55 | print(" ") 56 | } 57 | } 58 | 59 | // 第一轮:生成平铺 nodes 60 | public func parse() -> [XcodeprojNode] { 61 | var currentNodeType:XcodeprojNodeType = .normal 62 | var currentValue = "" 63 | var currentComment = "" 64 | var currentPState:PState = .normal 65 | 66 | // 方法内的方法能够共用方法内定义的变量 67 | func appendNode() { 68 | allNodes.append(XcodeprojNode(type: currentNodeType, value: currentValue, codeComment: currentComment, subNodes: [XcodeprojNode]())) 69 | currentNodeType = .normal 70 | currentValue = "" 71 | currentComment = "" 72 | } 73 | 74 | for tokens in allTokens { 75 | 76 | if currentPState == .normal { 77 | if tokens.type == .leftBrace { 78 | currentPState = .startDicKey 79 | currentNodeType = .dicStart 80 | appendNode() 81 | continue 82 | } 83 | if tokens.type == .semicolon { 84 | currentPState = .startDicKey 85 | continue 86 | } 87 | if tokens.type == .rightBrace { 88 | currentNodeType = .dicEnd 89 | appendNode() 90 | continue 91 | } 92 | } // end state normal 93 | 94 | if currentPState == .startDicKey { 95 | if tokens.type == .id { 96 | currentNodeType = .dicKey 97 | currentValue.append(tokens.tokens[0].des()) 98 | continue 99 | } 100 | if tokens.type == .codeComment { 101 | // 不能排 id 类型后 102 | if currentNodeType == .dicKey { 103 | currentComment.append(tokensToString(tokens: tokens.tokens)) 104 | } 105 | continue 106 | } 107 | if tokens.type == .equal { 108 | appendNode() 109 | currentPState = .startDicValue 110 | continue 111 | } 112 | if tokens.type == .leftBrace { 113 | currentNodeType = .dicStart 114 | appendNode() 115 | continue 116 | } 117 | if tokens.type == .rightBrace { 118 | currentNodeType = .dicEnd 119 | appendNode() 120 | continue 121 | } 122 | 123 | } // end start dic key 124 | 125 | 126 | // { key = value 127 | if currentPState == .startDicValue { 128 | 129 | if tokens.type == .id { 130 | currentNodeType = .dicValue 131 | currentValue.append(tokens.tokens[0].des()) 132 | continue 133 | } 134 | if tokens.type == .codeComment { 135 | currentComment.append(tokensToString(tokens: tokens.tokens)) 136 | continue 137 | } 138 | if tokens.type == .string { 139 | currentNodeType = .dicValue 140 | if tokens.tokens.count > 0 { 141 | currentValue.append("\(tokensToString(tokens: tokens.tokens))") 142 | } 143 | continue 144 | } 145 | if tokens.type == .semicolon { 146 | if currentNodeType == .dicValue { 147 | appendNode() 148 | } 149 | currentPState = .startDicKey 150 | continue 151 | } 152 | 153 | // 字典情况 154 | if tokens.type == .leftBrace { 155 | currentNodeType = .dicStart 156 | currentPState = .startDicKey 157 | appendNode() 158 | continue 159 | } 160 | if tokens.type == .rightBrace { 161 | currentNodeType = .dicEnd 162 | appendNode() 163 | continue 164 | } 165 | 166 | 167 | // 数组情况 168 | // ( 169 | if tokens.type == .leftParenthesis { 170 | currentNodeType = .arrStart 171 | appendNode() 172 | currentPState = .startArrValue 173 | continue 174 | } 175 | 176 | } // end dic value 177 | 178 | if currentPState == .startArrValue { 179 | if tokens.type == .id { 180 | currentNodeType = .arrValue 181 | currentValue.append(tokens.tokens[0].des()) 182 | continue 183 | } 184 | if tokens.type == .string { 185 | currentNodeType = .arrValue 186 | currentValue.append("\(tokensToString(tokens: tokens.tokens))") 187 | continue 188 | } 189 | if tokens.type == .codeComment { 190 | currentComment.append(tokensToString(tokens: tokens.tokens)) 191 | continue 192 | } 193 | if tokens.type == .comma { 194 | appendNode() 195 | continue 196 | } 197 | if tokens.type == .rightParenthesis { 198 | currentNodeType = .arrEnd 199 | appendNode() 200 | currentPState = .normal 201 | continue 202 | } 203 | } // end arr value 204 | 205 | } // end for 206 | 207 | return allNodes 208 | } 209 | 210 | // Mark: 私有方法 211 | 212 | 213 | private func tokensToString(tokens:[Token]) -> String { 214 | var reStr = "" 215 | for token in tokens { 216 | reStr.append(token.des()) 217 | } 218 | return reStr 219 | } 220 | 221 | 222 | } 223 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/JSON/TestJSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestJSON.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/10/25. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class TestJSON:Test { 12 | 13 | public static func testJSON() { 14 | let jsonOPath = Bundle.main.path(forResource: "test", ofType: "json") 15 | let jOrgPath = jsonOPath ?? "" 16 | 17 | let jsonOContent = FileHandle.fileContent(path: jOrgPath) 18 | 19 | let item = ParseJSONItem(input: jsonOContent).parse() 20 | 21 | let arr = item.array[0].kvs 22 | 23 | cs(current: "\(arr.count)", expect: "3", des: "all dic count") 24 | 25 | cs(current: "\(arr[0].key)", expect: "key1", des: "key1") 26 | cs(current: "\(arr[0].value.value)", expect: "value1", des: "value1") 27 | cs(current: "\(arr[1].key)", expect: "key2", des: "key2") 28 | cs(current: "\(arr[1].value.value)", expect: "22", des: "value2") 29 | cs(current: "\(arr[2].key)", expect: "key3", des: "key3") 30 | let arr2kvs = arr[2].value.kvs 31 | cs(current: "\(arr2kvs.count)", expect: "5", des: "arr2kvs count") 32 | cs(current: "\(arr2kvs[0].key)", expect: "subKey1", des: "subKey1") 33 | cs(current: "\(arr2kvs[0].value.value)", expect: "subValue1", des: "subValue1") 34 | cs(current: "\(arr2kvs[1].key)", expect: "subKey2", des: "subKey2") 35 | cs(current: "\(arr2kvs[1].value.value)", expect: "40", des: "subValue2") 36 | cs(current: "\(arr2kvs[2].key)", expect: "subKey3", des: "subKey3") 37 | let subValue3 = arr2kvs[2].value.array 38 | cs(current: "\(subValue3.count)", expect: "2", des: "subValue3 count") 39 | cs(current: "\(subValue3[0].kvs.count)", expect: "2", des: "subValue3 kvs count") 40 | let subValue30Kvs = subValue3[0].kvs 41 | cs(current: "\(subValue30Kvs[0].key)", expect: "sub1Key1", des: "sub1Key1") 42 | cs(current: "\(subValue30Kvs[0].value.value)", expect: "10", des: "sub1Key1 value") 43 | cs(current: "\(subValue30Kvs[1].key)", expect: "sub1Key2", des: "sub1Key2") 44 | let subValue30Kvs1ValueKvs = subValue30Kvs[1].value.kvs 45 | cs(current: "\(subValue30Kvs1ValueKvs.count)", expect: "2", des: "sub1Key2 value kvs count") 46 | cs(current: "\(subValue30Kvs1ValueKvs[0].key)", expect: "sub3Key1", des: "sub3Key1") 47 | cs(current: "\(subValue30Kvs1ValueKvs[0].value.value)", expect: "sub3Value1", des: "sub3Value1") 48 | cs(current: "\(subValue30Kvs1ValueKvs[1].key)", expect: "sub3Key2", des: "sub3Key2") 49 | cs(current: "\(subValue30Kvs1ValueKvs[1].value.value)", expect: "sub3Value2", des: "sub3Value2") 50 | 51 | let subValue31Kvs = subValue3[1].kvs 52 | cs(current: "\(subValue31Kvs[0].key)", expect: "sub1Key1", des: "subValue31Kvs sub1Key1") 53 | cs(current: "\(subValue31Kvs[0].value.value)", expect: "11", des: "subValue31Kvs sub1Key1 value") 54 | cs(current: "\(subValue31Kvs[1].key)", expect: "sub1Key2", des: "subValue31Kvs sub1Key2") 55 | cs(current: "\(subValue31Kvs[1].value.value)", expect: "15", des: "subValue31Kvs sub1Key2 value") 56 | 57 | cs(current: "\(arr2kvs[3].key)", expect: "subKey4", des: "subKey4") 58 | cs(current: "\(arr2kvs[3].value.array.count)", expect: "3", des: "subKey4 value array count") 59 | cs(current: "\(arr2kvs[3].value.array[0].value)", expect: "value1", des: "subKey4 value array 0 value") 60 | cs(current: "\(arr2kvs[3].value.array[1].value)", expect: "23", des: "subKey4 value array 1 value") 61 | cs(current: "\(arr2kvs[3].value.array[2].value)", expect: "value2", des: "subKey4 value array 2 value") 62 | 63 | cs(current: "\(arr2kvs[4].key)", expect: "subKey5", des: "subKey5") 64 | cs(current: "\(arr2kvs[4].value.value)", expect: "2", des: "subKey5 value") 65 | 66 | } 67 | 68 | public static func codeLines() { 69 | let jsonOld = FileHandle.fileContent(path: "/Users/ming/Downloads/data/CodeLines/1020_codelines.json") 70 | let jsonNew = FileHandle.fileContent(path: "/Users/ming/Downloads/data/CodeLines/1025_codelines.json") 71 | 72 | let itemOld = ParseJSONItem(input: jsonOld).parse() 73 | let itemNew = ParseJSONItem(input: jsonNew).parse() 74 | 75 | let oldBundles = itemOld.array[0].kvs[3].value.array 76 | let newBundles = itemNew.array[0].kvs[3].value.array 77 | 78 | var bizDic = [String:String]() 79 | 80 | func dicData(arr:[JSONItem]) -> [String:Int] { 81 | let filterTech = ["Java","ObjectiveC","ObjectiveC++","C/C++Header","C","C++","XML"] 82 | var dic = [String:Int]() 83 | 84 | for aBundle in arr { 85 | let bundleName = aBundle.kvs[0].value.value 86 | 87 | let biz = aBundle.kvs[1].value.value 88 | bizDic[bundleName] = biz 89 | // let bizDetail = aBundle.kvs[3].value.value 90 | let platform = aBundle.kvs[4].value.value 91 | 92 | if platform == "ios" || platform == "android" { 93 | // 94 | } else { 95 | continue 96 | } 97 | // print(bundleName) 98 | let classifys = aBundle.kvs[5].value.array 99 | for aClassify in classifys { 100 | let kvs = aClassify.kvs 101 | var code = 0 102 | var name = "" 103 | for akv in kvs { 104 | if akv.key == "code" { 105 | code = Int(akv.value.value) ?? 0 106 | } else if akv.key == "name" { 107 | name = akv.value.value 108 | } 109 | 110 | } 111 | // print("code:\(code) name:\(name)") 112 | if filterTech.contains(name) { 113 | if dic[bundleName] != nil { 114 | dic[bundleName]! += code 115 | } else { 116 | dic[bundleName] = code 117 | } 118 | } // end if filterTech.contains(name) 119 | 120 | } // end for aClassify in classifys 121 | } // end for aBundle in oldBundles 122 | return dic 123 | } 124 | 125 | let oldDic = dicData(arr: oldBundles) 126 | let newDic = dicData(arr: newBundles) 127 | 128 | var totalOld = 0 129 | for (_,v) in oldDic { 130 | totalOld += v 131 | } 132 | var totalNew = 0 133 | 134 | var moreBundle = "" 135 | var moreCode = [String:Int]() 136 | var lessCode = [String:Int]() 137 | var moreTotal = 0 138 | var lessTotal = 0 139 | for (k,v) in newDic { 140 | let kBiz = bizDic[k] 141 | let kDes = "\(k)[\(kBiz ?? "")]" 142 | totalNew += v 143 | if oldDic[k] == nil { 144 | moreBundle.append("新增:\(kDes) \(v)\r\n") 145 | } else { 146 | let oldValue = oldDic[k] ?? 0 147 | let diff = v - oldValue 148 | if diff > 0 { 149 | moreCode["\(kDes)多了\(diff)"] = diff 150 | moreTotal += diff 151 | } else if diff < 0 { 152 | lessCode["\(kDes)少了\(diff)"] = diff 153 | lessTotal += diff 154 | } 155 | } // end if 156 | 157 | } // end for 158 | 159 | print(moreBundle) 160 | let sortMoreCode = moreCode.sortedByValue 161 | let sortLessCode = lessCode.sortedByValue 162 | for (k,_) in sortMoreCode { 163 | print(k) 164 | } 165 | print(" ") 166 | for (k,_) in sortLessCode { 167 | print(k) 168 | } 169 | print(" ") 170 | // print(moreCode) 171 | // print(lessCode) 172 | print("共多了:\(moreTotal)") 173 | print("共少了:\(lessTotal)") 174 | print("旧总量:\(totalOld)") 175 | print("新总量:\(totalNew)") 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/ParseOCTokens.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseOCTokens.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/11/11. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | // 分词:http://clang.llvm.org/doxygen/Lexer_8cpp_source.html 10 | 11 | import Foundation 12 | 13 | // MARK: OCTokenNode 14 | public struct OCTokenNode { 15 | public let type: OCTK 16 | public let line: Int 17 | public let value: String 18 | } 19 | 20 | public class ParseOCTokens { 21 | // MARK: 状态定义 22 | private enum State { 23 | case normal 24 | case commentStart 25 | case commentStarStart 26 | case stringStart 27 | case stringWideStart 28 | case stringAngleStart 29 | case charStart 30 | } 31 | // MARK: 属性:集合 32 | 33 | private var tokens: [Token] 34 | private var ocTkNodes: [OCTokenNode] 35 | 36 | // MARK: 属性:当前 37 | private var currentIndex: Int 38 | private var currentToken: Token // 当前处理的 token 39 | private var currentState: State // 当前解析状态 40 | private var currentTokens: [Token] // 当前 token 集 41 | private var currentLine: Int // 当前行数 42 | private var currentValue: String // 当前值 43 | 44 | // 输入源码内容 45 | public init(input: String) { 46 | 47 | tokens = Lexer(input: input, type: .plain).allTkFast(operaters: "[](){}.&=*+-<>~!/%^|?:;,#\\\"@$'") 48 | ocTkNodes = [OCTokenNode]() 49 | 50 | currentIndex = 0 51 | currentToken = tokens[currentIndex] 52 | currentState = .normal 53 | currentTokens = [Token]() 54 | currentLine = 0 55 | currentValue = "" 56 | } 57 | 58 | public func parse() -> [OCTokenNode] { 59 | while currentToken != .eof { 60 | parseNext() 61 | } 62 | // for node in ocTkNodes { 63 | // print(node.line) 64 | // print(node.value) 65 | // } 66 | return ocTkNodes 67 | } 68 | 69 | private func addToken(type:OCTK) { 70 | ocTkNodes.append(OCTokenNode(type: type, line: currentLine, value: currentValue)) 71 | currentValue = "" 72 | currentState = .normal 73 | } 74 | 75 | private func parseNext() { 76 | 77 | 78 | // MARK: 一般状态的处理 79 | if currentState == .normal { 80 | if currentToken == .newLine { 81 | currentLine += 1 82 | addToken(type: .eod) 83 | advanceTk() 84 | return 85 | } 86 | // MARK: 处理注释情况 87 | if currentToken == .id("#") && peekTk() == .id("pragma") && peekTkStep(step: 2) == .space && peekTkStep(step: 3) == .id("mark") { 88 | currentState = .commentStart 89 | advanceTk() 90 | advanceTk() 91 | advanceTk() 92 | return 93 | } 94 | if currentToken == .id("/") { 95 | if peekTk() == .id("/") { 96 | currentState = .commentStart 97 | advanceTk() 98 | advanceTk() 99 | return 100 | } 101 | if peekTk() == .id("*") { 102 | currentState = .commentStarStart 103 | advanceTk() 104 | advanceTk() 105 | return 106 | } 107 | } 108 | // MARK: 处理字符串 109 | if currentToken == .id("@") && peekTk() == .id("\"") { 110 | currentState = .stringWideStart 111 | advanceTk() 112 | advanceTk() 113 | return 114 | } 115 | if currentToken == .id("\"") { 116 | currentState = .stringStart 117 | advanceTk() 118 | return 119 | } 120 | if currentToken == .id("'") { 121 | currentState = .charStart 122 | advanceTk() 123 | return 124 | } 125 | // MARK: 空格、换行 126 | if currentToken == .space { 127 | advanceTk() 128 | return 129 | } 130 | 131 | // normal 时默认处理 132 | currentValue = currentToken.des() 133 | addToken(type: .identifier) 134 | advanceTk() 135 | return 136 | } // end if currentState == .normal 137 | 138 | // MARK: 注释 139 | if currentState == .commentStart { 140 | while true { 141 | if currentToken == .newLine || currentToken == .eof { 142 | currentLine += 1 143 | advanceTk() 144 | break 145 | } else { 146 | currentValue += currentToken.des() 147 | advanceTk() 148 | } 149 | } // end while 150 | addToken(type: .comment) 151 | return 152 | } 153 | 154 | if currentState == .commentStarStart { 155 | 156 | while true { 157 | if currentToken.des() == "*" && peekTk()?.des() == "/" { 158 | advanceTk() 159 | advanceTk() 160 | break 161 | } else { 162 | if currentToken == .newLine { 163 | currentLine += 1 164 | } 165 | currentValue += currentToken.des() 166 | advanceTk() 167 | } 168 | } 169 | addToken(type: .comment) 170 | return 171 | } 172 | 173 | // MARK: 字符串 174 | if currentState == .charStart { 175 | while true { 176 | if currentToken.des() == "\\" && peekTk()?.des() == "\\" { 177 | advanceTk() 178 | advanceTk() 179 | currentValue += "\\" 180 | } else if currentToken.des() == "\\" && peekTk()?.des() == "'" { 181 | advanceTk() 182 | advanceTk() 183 | currentValue += "'" 184 | } else if currentToken.des() == "'" || currentToken == .newLine { 185 | advanceTk() 186 | break 187 | } else { 188 | currentValue += currentToken.des() 189 | advanceTk() 190 | } 191 | } // end while 192 | addToken(type: .charConstant) 193 | return 194 | } 195 | if currentState == .stringStart || currentState == .stringWideStart { 196 | while true { 197 | if currentToken.des() == "\\" && peekTk()?.des() == "\\" { 198 | advanceTk() 199 | advanceTk() 200 | currentValue += "\\" 201 | } else if currentToken.des() == "\\" && peekTk()?.des() == "\"" { 202 | advanceTk() 203 | advanceTk() 204 | currentValue += "\"" 205 | } else if currentToken.des() == "\"" || currentToken == .newLine { 206 | advanceTk() 207 | break 208 | } else { 209 | currentValue += currentToken.des() 210 | advanceTk() 211 | } 212 | } // end while 213 | 214 | if currentState == .stringStart { 215 | addToken(type: .stringLiteral) 216 | } 217 | if currentState == .stringWideStart { 218 | addToken(type: .wideStringLiteral) 219 | } 220 | return 221 | }// end if currentState == .stringStart || currentState == .stringWideStart 222 | 223 | currentValue = currentToken.des() 224 | addToken(type: .identifier) 225 | advanceTk() 226 | return 227 | 228 | } // end func 229 | 230 | // MARK: 辅助 231 | private func peekTk() -> Token? { 232 | return peekTkStep(step: 1) 233 | } 234 | 235 | private func peekTkStep(step: Int) -> Token? { 236 | let peekIndex = currentIndex + step 237 | guard peekIndex < tokens.count else { 238 | return nil 239 | } 240 | return tokens[peekIndex] 241 | } 242 | 243 | private func advanceTk() { 244 | currentIndex += 1 245 | guard currentIndex < tokens.count else { 246 | return 247 | } 248 | currentToken = tokens[currentIndex] 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/Core/Lexer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Lexer.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/8/2. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public enum LexerType { 12 | case code 13 | case plain 14 | } 15 | 16 | public class Lexer { 17 | 18 | private let text: String 19 | private var currentIndex: Int 20 | private var currentCharacter: Character? 21 | private var type: LexerType 22 | 23 | public init(input: String, type: LexerType) { 24 | if input.count == 0 { 25 | //fatalError("Error! input can't be empty") 26 | text = "0" 27 | } else { 28 | text = input 29 | } 30 | currentIndex = 0 31 | currentCharacter = text[text.startIndex] 32 | self.type = type 33 | 34 | } 35 | 36 | public func allTkFastWithoutNewLineAndWhitespace(operaters:String) -> [Token] { 37 | let allToken = allTkFast(operaters: operaters) 38 | let flAllToken = allToken.filter { 39 | $0 != .newLine 40 | } 41 | let fwAllToken = flAllToken.filter { 42 | $0 != .space 43 | } 44 | return fwAllToken 45 | } 46 | 47 | public func allTkFast(operaters:String) -> [Token] { 48 | var nText = text.replacingOccurrences(of: " ", with: " starmingspace ") 49 | nText = nText.replacingOccurrences(of: "\n", with: " starmingnewline ") 50 | let scanner = Scanner(string: nText) 51 | var tks = [Token]() 52 | var set = CharacterSet() 53 | set.insert(charactersIn: operaters) 54 | set.formUnion(CharacterSet.whitespacesAndNewlines) 55 | 56 | while !scanner.isAtEnd { 57 | for operater in operaters { 58 | let opStr = operater.description 59 | if (scanner.scanString(opStr) != nil) { 60 | tks.append(.id(opStr)) 61 | } 62 | } 63 | 64 | if let result = scanner.scanUpToCharacters(from: set) { 65 | let resultString = result as String 66 | if resultString == "starmingnewline" { 67 | tks.append(.newLine) 68 | } else if resultString == "starmingspace" { 69 | tks.append(.space) 70 | } else { 71 | tks.append(.id(result as String)) 72 | } 73 | } 74 | } 75 | tks.append(.eof) 76 | return tks 77 | } 78 | 79 | // 返回所有 Token 80 | public func allTk() -> [Token] { 81 | var tk = nextTk() 82 | var all = [tk] 83 | while tk != .eof { 84 | tk = self.nextTk() 85 | all.append(tk) 86 | } 87 | return all 88 | } 89 | 90 | // 流程 91 | private func nextTk() -> Token { 92 | // 检查是否到达文件末 93 | if isEof() { 94 | return .eof 95 | } 96 | 97 | if CharacterSet.whitespaces.contains((currentCharacter?.unicodeScalars.first!)!) { 98 | skipWhiteSpace() 99 | } 100 | 101 | // 检查是否到达文件末 102 | if isEof() { 103 | return .eof 104 | } 105 | 106 | // 换行 107 | if CharacterSet.newlines.contains((currentCharacter?.unicodeScalars.first!)!) { 108 | advance() 109 | return .newLine 110 | } 111 | 112 | // 数字 113 | if CharacterSet.decimalDigits.contains((currentCharacter?.unicodeScalars.first!)!) { 114 | let n = number() 115 | print(n.des()) 116 | return n 117 | } 118 | 119 | // 字符 120 | if CharacterSet.alphanumerics.contains((currentCharacter?.unicodeScalars.first!)!) { 121 | return id() 122 | } 123 | 124 | // 代码分析 125 | if type == .code { 126 | 127 | 128 | // 双引号内字符串 129 | if currentCharacter == "\"" { 130 | return doubleQuotationMarksString() 131 | } 132 | 133 | // 处理注释 134 | if currentCharacter == "/" { 135 | // 双引号注释 136 | if peek() == "/" { 137 | advance() 138 | advance() 139 | return commentsFromDoubleSlash() 140 | } else if peek() == "*" { 141 | advance() 142 | advance() 143 | return commentsFromSlashAsterisk() 144 | } 145 | } 146 | } 147 | 148 | // 其余当作符号处理 149 | guard let cStr = currentCharacter else { 150 | return .eof 151 | } 152 | advance() 153 | return .id(String(cStr)) 154 | 155 | // 需要处理严格规则的时候会走下面条件 156 | // advance() 157 | // return .eof 158 | } 159 | 160 | // 对字符的处理 161 | private func id() -> Token { 162 | var idStr = "" 163 | while let character = currentCharacter, CharacterSet.alphanumerics.contains(character.unicodeScalars.first!) { 164 | idStr += String(character) 165 | advance() 166 | } 167 | return .id(idStr) 168 | } 169 | 170 | // 对数字的处理 171 | private func number() -> Token { 172 | var numStr = "" 173 | while let character = currentCharacter,CharacterSet.decimalDigits.contains(character.unicodeScalars.first!) { 174 | numStr += String(character) 175 | advance() 176 | } 177 | 178 | if let character = currentCharacter, character == ".", peek() != "." { 179 | numStr += "." 180 | advance() 181 | while let character = currentCharacter, CharacterSet.decimalDigits.contains(character.unicodeScalars.first!) { 182 | numStr += String(character) 183 | advance() 184 | } 185 | return .constant(.float(Float(numStr)!)) 186 | } 187 | return .constant(.integer(Int(numStr)!)) 188 | } 189 | 190 | // MARK: 辅助函数 191 | private func advance() { 192 | currentIndex += 1 193 | guard currentIndex < text.count else { 194 | currentCharacter = nil 195 | return 196 | } 197 | currentCharacter = text[text.index(text.startIndex, offsetBy: currentIndex)] 198 | } 199 | 200 | // 往前探一个字符 201 | private func peek() -> String? { 202 | return peekStep(step: 1) 203 | } 204 | private func peekStep(step:Int) -> String? { 205 | var reStr = "" 206 | for index in 1.. Token { 218 | var cStr = "" 219 | while let character = currentCharacter, !CharacterSet.newlines.contains(character.unicodeScalars.first!) { 220 | advance() 221 | cStr += String(character) 222 | } 223 | return .comments(cStr) 224 | } 225 | 226 | // 取 /* */ 这样的注释 227 | private func commentsFromSlashAsterisk() -> Token { 228 | var cStr = "" 229 | while let character = currentCharacter { 230 | if character == "*" && peek() == "/" { 231 | advance() 232 | advance() 233 | break 234 | } else { 235 | advance() 236 | cStr += String(character) 237 | } 238 | 239 | } 240 | return .comments(cStr) 241 | } 242 | 243 | // 双引号内字符串 244 | private func doubleQuotationMarksString() -> Token { 245 | advance() 246 | var cStr = "" 247 | while let character = currentCharacter { 248 | if character == "\\" && peek() == "\"" { 249 | advance() 250 | advance() 251 | cStr += String("\"") 252 | } else if character == "\"" { 253 | advance() 254 | break 255 | } else { 256 | advance() 257 | cStr += String(character) 258 | } 259 | } 260 | return .string(cStr) 261 | } 262 | 263 | // 跳过空格 264 | private func skipWhiteSpace() { 265 | while let character = currentCharacter, CharacterSet.whitespacesAndNewlines.contains(character.unicodeScalars.first!) { 266 | advance() 267 | } 268 | } 269 | 270 | 271 | 272 | private func isEof() -> Bool { 273 | if currentIndex > self.text.count - 1 { 274 | return true 275 | } 276 | return false 277 | } 278 | 279 | } 280 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/OC/ParseOCTokensDefine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParseOCTokensDefine.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/11/14. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // 官方 token 定义:https://opensource.apple.com//source/lldb/lldb-69/llvm/tools/clang/include/clang/Basic/TokenKinds.def 12 | 13 | // 切割符号 [](){}.&=*+-<>~!/%^|?:;,#@ 14 | public enum OCTK { 15 | case unknown // 不是 token 16 | case eof // 文件结束 17 | case eod // 行结束 18 | case codeCompletion // Code completion marker 19 | case cxxDefaultargEnd // C++ default argument end marker 20 | case comment // 注释 21 | case identifier // 比如 abcde123 22 | case numericConstant(OCTkNumericConstant) // 整型、浮点 0x123,解释计算时用,分析代码时可不用 23 | case charConstant // 'a' 24 | case stringLiteral // "foo" 25 | case wideStringLiteral // L"foo" 26 | case angleStringLiteral // 待处理需要考虑作为小于符号的问题 27 | 28 | // 标准定义部分 29 | // 标点符号 30 | case punctuators(OCTkPunctuators) 31 | 32 | // 关键字 33 | case keyword(OCTKKeyword) 34 | 35 | // @关键字 36 | case atKeyword(OCTKAtKeyword) 37 | } 38 | 39 | extension OCTK: Equatable { 40 | public static func == (lhs: OCTK, rhs: OCTK) -> Bool { 41 | switch (lhs, rhs) { 42 | case (.eof, .eof): 43 | return true 44 | case (.eod, .eod): 45 | return true 46 | case (.comment, .comment): 47 | return true 48 | case (.identifier, .identifier): 49 | return true 50 | case (.stringLiteral, .stringLiteral): 51 | return true 52 | case (.wideStringLiteral, .wideStringLiteral): 53 | return true 54 | default: 55 | return false 56 | } 57 | } 58 | } 59 | 60 | // MARK: @关键字 61 | public enum OCTKAtKeyword { 62 | case notKeyword 63 | case `class` 64 | case compatibilityAlias 65 | case defs 66 | case encode 67 | case end 68 | case implementation 69 | case interface 70 | case `private` 71 | case protected 72 | case `protocol` 73 | case `public` 74 | case selector 75 | case `throw` 76 | case `try` 77 | case `catch` 78 | case finally 79 | case synchronized 80 | case property 81 | case package 82 | case required 83 | case optional 84 | case synthesize 85 | case dynamic 86 | 87 | } 88 | 89 | // MARK:关键字 90 | public enum OCTKKeyword { 91 | // C99 6.4.1: 92 | case auto 93 | case `break` 94 | case `case` 95 | case char 96 | case const 97 | case `continue` 98 | case `default` 99 | case `do` 100 | case double 101 | case `else` 102 | case `enum` 103 | case extern 104 | case float 105 | case `for` 106 | case goto 107 | case `if` 108 | case inline 109 | case int 110 | case long 111 | case register 112 | case restrict 113 | case `return` 114 | case short 115 | case signed 116 | case sizeof 117 | case `static` 118 | case `struct` 119 | case `switch` 120 | case typedef 121 | case union 122 | case unsigned 123 | case void 124 | case volatile 125 | case `while` 126 | case _Bool 127 | case _Generic 128 | case _Imaginary 129 | case _Static_assert 130 | case __func__ 131 | 132 | // C++ 2.11p1: Keywords. 133 | case asm 134 | case bool 135 | case `catch` 136 | case `class` 137 | case const_cast 138 | case delete 139 | case dynamic_cast 140 | case explicit 141 | case export 142 | case `false` 143 | case friend 144 | case mutable 145 | case namespace 146 | case new 147 | case `operator` 148 | case `private` 149 | case protected 150 | case `public` 151 | case reinterpret_cast 152 | case static_cast 153 | case template 154 | case this 155 | case `throw` 156 | case `true` 157 | case `try` 158 | case typename 159 | case typeid 160 | case using 161 | case virtual 162 | case wchar_t 163 | 164 | // C++ 2.5p2: Alternative Representations. 165 | case and 166 | case and_eq 167 | case bitand 168 | case bitor 169 | case compl 170 | case not 171 | case not_eq 172 | case or 173 | case or_eq 174 | case xor 175 | case xor_eq 176 | 177 | // C++0x keywords 178 | case alignof 179 | case char16_t 180 | case char32_t 181 | case constexpr 182 | case decltype 183 | case noexcept 184 | case nullptr 185 | case static_assert 186 | case thread_local 187 | 188 | // GNU Extensions (in impl-reserved namespace) 189 | case _Decimal32 190 | case _Decimal64 191 | case _Decimal128 192 | case __null 193 | case __alignof 194 | case __attribute 195 | case __builtin_choose_expr 196 | case __builtin_types_compatible_p 197 | case __builtin_va_arg 198 | case __extension__ 199 | case __imag 200 | case __label__ 201 | case __real 202 | case __thread 203 | case `__FUNCTION__` 204 | case __PRETTY_FUNCTION__ 205 | 206 | // GNU Extensions (outside impl-reserved namespace) 207 | case typeof 208 | 209 | // GNU and MS Type Traits 210 | case __has_nothrow_assign 211 | case __has_nothrow_copy 212 | case __has_nothrow_constructor 213 | case __has_trivial_assign 214 | case __has_trivial_copy 215 | case __has_trivial_constructor 216 | case __has_trivial_destructor 217 | case __has_virtual_destructor 218 | case __is_abstract 219 | case __is_base_of 220 | case __is_class 221 | case __is_convertible_to 222 | case __is_empty 223 | case __is_enum 224 | 225 | // Tentative name - there's no implementation of std::is_literal_type yet. 226 | case __is_literal 227 | 228 | // Name for GCC 4.6 compatibility - people have already written libraries using this name unfortunately. 229 | case __is_literal_type 230 | case __is_pod 231 | case __is_polymorphic 232 | case __is_trivial 233 | case __is_union 234 | 235 | // Clang-only C++ Type Traits 236 | case __is_trivially_copyable 237 | case __underlying_type 238 | 239 | // Embarcadero Expression Traits 240 | case __is_lvalue_expr 241 | case __is_rvalue_expr 242 | 243 | // Embarcadero Unary Type Traits 244 | case __is_arithmetic 245 | case __is_floating_point 246 | case __is_integral 247 | case __is_complete_type 248 | case __is_void 249 | case __is_array 250 | case __is_function 251 | case __is_reference 252 | case __is_lvalue_reference 253 | case __is_rvalue_reference 254 | case __is_fundamental 255 | case __is_object 256 | case __is_scalar 257 | case __is_compound 258 | case __is_pointer 259 | case __is_member_object_pointer 260 | case __is_member_function_pointer 261 | case __is_member_pointer 262 | case __is_const 263 | case __is_volatile 264 | case __is_standard_layout 265 | case __is_signed 266 | case __is_unsigned 267 | 268 | // Embarcadero Binary Type Traits 269 | case __is_same 270 | case __is_convertible 271 | case __array_rank 272 | case __array_extent 273 | 274 | // Apple Extension. 275 | case __private_extern__ 276 | 277 | // Microsoft Extension. 278 | case __declspec 279 | case __cdecl 280 | case __stdcall 281 | case __fastcall 282 | case __thiscall 283 | case __forceinline 284 | 285 | // OpenCL-specific keywords 286 | case __kernel 287 | case kernel 288 | case vec_step 289 | case __private 290 | case __global 291 | case __local 292 | case __constant 293 | case global 294 | case local 295 | case constant 296 | case __read_only 297 | case __write_only 298 | case __read_write 299 | case read_only 300 | case write_only 301 | case read_write 302 | 303 | // Borland Extensions. 304 | case __pascal 305 | 306 | // Altivec Extension. 307 | case __vector 308 | case __pixel 309 | 310 | // Alternate spelling for various tokens. There are GCC extensions in all languages, but should not be disabled in strict conformance mode. 311 | case __alignof__ 312 | case __asm 313 | case __asm__ 314 | case __attribute__ 315 | case __complex 316 | case __complex__ 317 | case __const 318 | case __decltype 319 | case __imag__ 320 | case __inline 321 | case __inline__ 322 | case __nullptr 323 | case __real__ 324 | case __restrict 325 | case __restrict__ 326 | case __signed 327 | case __signed__ 328 | case __typeof 329 | case __typeof__ 330 | case __volatile 331 | case __volatile__ 332 | 333 | // Microsoft extensions which should be disabled in strict conformance mode 334 | case __ptr64 335 | case __w64 336 | case __uuidof 337 | case __try 338 | case __except 339 | case __finally 340 | case __leave 341 | case __int64 342 | case __if_exists 343 | case __if_not_exists 344 | case __int8 345 | case __int32 346 | case _asm 347 | case _cdecl 348 | case _fastcall 349 | case _stdcall 350 | case _thiscall 351 | case _uuidof 352 | case _inline 353 | case _declspec 354 | case __interface 355 | 356 | // Borland Extensions which should be disabled in strict conformance mode. 357 | case _pascal 358 | 359 | // Clang Extensions. 360 | case __char16_t 361 | case __char32_t 362 | 363 | // Clang-specific keywords enabled only in testing. 364 | case __unknown_anytype 365 | } 366 | 367 | // MARK:标点符号 368 | public enum OCTkPunctuators { 369 | // C99 6.4.6: Punctuators 370 | case lSquare // [ 371 | case rSquare // ] 372 | case lParen // ( 373 | case rParen // ) 374 | case lBrace // { 375 | case rBrace // } 376 | case period // . 377 | case ellipsis // ... 378 | case amp // & 379 | case ampamp // && 380 | case ampequal // &= 381 | case star // * 382 | case starequal // *= 383 | case plus // + 384 | case plusplus // ++ 385 | case plusequal // += 386 | case minus // - 387 | case arrow // -> 388 | case minusminus // -- 389 | case minusequal // -= 390 | case tilde // ~ 391 | case exclaim // ! 392 | case exclaimequal // != 393 | case slash // / 394 | case slashequal // /= 395 | case percent // % 396 | case percentequal // %= 397 | case less // < 398 | case lessless // << 399 | case lessequal // <= 400 | case lesslessequal // <<= 401 | case greater // > 402 | case greatergreater // >> 403 | case greaterequal // >= 404 | case greatergreaterequal // >>= 405 | case caret // ^ 406 | case caretequal // ^= 407 | case pipe // | 408 | case pipepipe // || 409 | case pipeequal // |= 410 | case question // ? 411 | case colon // : 412 | case semi // ; 413 | case equal // = 414 | case equalequal // == 415 | case comma // , 416 | case hash // # 417 | case hashhash // ## 418 | case hashhat // #@ 419 | 420 | // C++ 支持 421 | case periodstar // .* 422 | case arrowstar // ->* 423 | case coloncolon // :: 424 | 425 | // Objective C support 426 | case at // @ 427 | 428 | // CUDA support 429 | case lesslessless // <<< 430 | case greatergreatergreater // >>> 431 | 432 | } 433 | 434 | // MARK:数字 435 | public enum OCTkNumericConstant { 436 | case integer(Int) 437 | case float(Float) 438 | } 439 | -------------------------------------------------------------------------------- /MethodTraceAnalyze/XML/TestXML.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestXcodeWorkspace.swift 3 | // SA 4 | // 5 | // Created by ming on 2019/9/25. 6 | // Copyright © 2019 ming. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class TestXML: Test { 12 | 13 | // MARK: Subscription 14 | public static func testSubscription() { 15 | let sPath = Bundle.main.path(forResource: "subscriptionSimple", ofType: "xml") 16 | let subscriptionPath = sPath ?? "" 17 | 18 | let sContent = FileHandle.fileContent(path: subscriptionPath) 19 | let root = ParseStandXML(input: sContent).parse() 20 | 21 | // check list 用来检查是否完整 22 | var checkList = [ 23 | "title": false, 24 | "link1": false, 25 | "link2": false, 26 | "updated": false, 27 | "id": false, 28 | "author": false, 29 | "generator": false, 30 | "entry_title": false, 31 | "entry_link": false, 32 | "entry_id": false, 33 | "entry_published": false, 34 | "entry_updated": false, 35 | "entry_content": false, 36 | "entry_summary": false, 37 | "entry_category1": false, 38 | "entry_category2": false, 39 | "entry_category3": false, 40 | ] as [String : Bool] 41 | 42 | // root 检查具体项是否准确 43 | cs(current: "\(root.subNodes.count)", expect: "2", des: "root.subNodes.count") 44 | for rootNode in root.subNodes { 45 | // feed 46 | if rootNode.name == "feed" { 47 | cs(current: "\(rootNode.subNodes.count)", expect: "8", des: "feed subNodes.count") 48 | var linkCount = 0 49 | for feedNode in rootNode.subNodes { 50 | 51 | if feedNode.name == "title" { 52 | cs(current: feedNode.value, expect: "星光社 - 戴铭的博客", des: "title") 53 | checkList["title"] = true 54 | continue 55 | } 56 | 57 | if feedNode.name == "link" { 58 | if linkCount == 0 { 59 | for at in feedNode.attributes { 60 | if at.name == "href" { 61 | cs(current: at.value, expect: "/atom.xml", des: "link 0 href value") 62 | checkList["link1"] = true 63 | } 64 | if at.name == "rel" { 65 | cs(current: at.value, expect: "self", des: "link 0 rel value") 66 | checkList["link1"] = true 67 | } 68 | } 69 | } // end link count 70 | if linkCount == 1 { 71 | for at in feedNode.attributes { 72 | if at.name == "href" { 73 | cs(current: at.value, expect: "http://ming1016.github.io/", des: "link 1 href value") 74 | checkList["link2"] = true 75 | } 76 | } 77 | } 78 | linkCount += 1 79 | continue 80 | } // end if link 81 | 82 | if feedNode.name == "updated" { 83 | cs(current: feedNode.value, expect: "2019-07-28T16:52:44.082Z", des: "update") 84 | checkList["updated"] = true 85 | continue 86 | } 87 | if feedNode.name == "id" { 88 | cs(current: feedNode.value, expect: "http://ming1016.github.io/", des: "id") 89 | checkList["id"] = true 90 | continue 91 | } 92 | if feedNode.name == "author" { 93 | cs(current: "\(feedNode.subNodes.count)", expect: "1", des: "author subNodes.count") 94 | cs(current: feedNode.subNodes[0].value, expect: "戴铭", des: "author name value") 95 | checkList["author"] = true 96 | continue 97 | } 98 | if feedNode.name == "generator" { 99 | for at in feedNode.attributes { 100 | if at.name == "uri" { 101 | cs(current: at.value, expect: "http://hexo.io/", des: "generator uri") 102 | checkList["generator"] = true 103 | } 104 | } 105 | cs(current: feedNode.value, expect: "Hexo", des: "generator value") 106 | checkList["generator"] = true 107 | continue 108 | } 109 | if feedNode.name == "entry" { 110 | for entryNode in feedNode.subNodes { 111 | if entryNode.name == "title" { 112 | cs(current: entryNode.value, expect: "iOS 开发舆图", des: "entry title") 113 | checkList["entry_title"] = true 114 | continue 115 | } 116 | if entryNode.name == "link" { 117 | for at in entryNode.attributes { 118 | if at.name == "href" { 119 | cs(current: at.value, expect: "http://ming1016.github.io/2019/07/29/ios-map/", des: "entry link href") 120 | checkList["entry_link"] = true 121 | } 122 | } 123 | continue 124 | } 125 | if entryNode.name == "id" { 126 | cs(current: entryNode.value, expect: "http://ming1016.github.io/2019/07/29/ios-map/", des: "entry id") 127 | checkList["entry_id"] = true 128 | continue 129 | } 130 | if entryNode.name == "published" { 131 | cs(current: entryNode.value, expect: "2019-07-29T04:49:06.000Z", des: "entry published") 132 | checkList["entry_published"] = true 133 | continue 134 | } 135 | if entryNode.name == "updated" { 136 | cs(current: entryNode.value, expect: "2019-07-28T16:52:44.082Z", des: "entry updated") 137 | checkList["entry_updated"] = true 138 | continue 139 | } 140 | if entryNode.name == "content" { 141 | for at in entryNode.attributes { 142 | if at.name == "type" { 143 | cs(current: at.value, expect: "html", des: "entry content type") 144 | checkList["entry_content"] = true 145 | } 146 | } 147 | 148 | let entryNodeContent = """ 149 |

43篇 《iOS开发高手课》已完成,后面会对内容进行迭代,丰富下内容和配图。最近画了张 iOS 开发全景舆图,还有相关一些资料整理,方便我平时开发 App 时参看。舆图如下:

\"\"
\"\"
\"\"
\"\"
\"\"
\"\"

接下来,我按照 iOS 开发地图的顺序,和你推荐一些相关的学习资料。

实例

学习 iOS 开发最好是从学习一个完整的 App 入手,GitHub上的Open-Source iOS Apps
项目,收录了大量开源的完整 App 例子,比如 Hacker News Reader 等已经上架了 App Store 的应用程序,所有例子都会标注是否上架 App Store的、所使用开发语言、推荐等级等信息,有利于进行选择学习。

150 | """ 151 | cs(current: entryNode.value, expect: entryNodeContent, des: "entry content value") 152 | checkList["entry_content"] = true 153 | continue 154 | } 155 | if entryNode.name == "summary" { 156 | for at in entryNode.attributes { 157 | if at.name == "type" { 158 | cs(current: at.value, expect: "html", des: "entry summary type") 159 | } 160 | } 161 | let entryNodeSummaryValue = """ 162 | <p>43篇 <a href="https://time.geekbang.org/column/intro/161" target="_blank" rel="external">《iOS开发高手课》</a>已完成,后面会对内容进行迭代,丰富下内容和配图。最近画了张 iOS 开 163 | """ 164 | cs(current: entryNode.value, expect: entryNodeSummaryValue, des: "entry summary value") 165 | checkList["entry_summary"] = true 166 | continue 167 | } 168 | 169 | if entryNode.name == "category" { 170 | for at in entryNode.attributes { 171 | if at.name == "term" { 172 | if at.value == "Programming" { 173 | checkList["entry_category1"] = true 174 | } 175 | if at.value == "iOS" { 176 | checkList["entry_category2"] = true 177 | } 178 | if at.value == "Swift" { 179 | checkList["entry_category3"] = true 180 | } 181 | } // end if 182 | } // end for 183 | } // end if category 184 | 185 | } // end for entryNode in feedNode.subNodes 186 | continue 187 | } // end if feed 188 | 189 | 190 | 191 | 192 | } // end rootNode subNodes 193 | } // end if feed 194 | 195 | } // end root subNodes 196 | 197 | // 检查 checklist 是否完整 198 | for (k,v) in checkList { 199 | if v == false { 200 | fatalError("❌ \(k) checklist no pass") 201 | } 202 | } 203 | 204 | } // end func 205 | 206 | 207 | // MARK: XcodeWorkspace 208 | public static func testXcodeWorkspace() { 209 | let workspacePath = Bundle.main.path(forResource: "contents", ofType: "xcworkspacedata") 210 | let wPath = workspacePath ?? "" 211 | 212 | let wContent = FileHandle.fileContent(path: wPath) 213 | let root = ParseStandXML(input: wContent).parse() 214 | 215 | cs(current: "\(root.subNodes.count)", expect: "2", des: "root.subNodes.count") 216 | for i in 0.. OCNode { 84 | var pNode = defaultOCNode() 85 | pNode.type = .root 86 | let rootNode = parseNode(parentNode: pNode, nodes: tokenNodes) 87 | return rootNode 88 | } 89 | 90 | // MARK: 递归解析状态 91 | private enum RState { 92 | case normal 93 | case eod // 换行 94 | 95 | // 方法 96 | case methodStart // 方法开始 97 | case methodReturnStart // 方法返回类型开始 98 | case methodReturnEnd // 方法返回类型结束 99 | case methodNameEnd // 方法名结束 100 | case methodParamStart // 方法参数开始 101 | case methodContentStart // 方法内容开始 102 | case methodParamTypeStart // 方法参数类型开始 103 | case methodParamTypeEnd // 方法参数类型结束 104 | case methodParamEnd // 方法参数结束 105 | case methodParamNameEnd // 方法参数名结束 106 | case methodShouldEnd // 针对方法定义的情况 比如 interface 里的 - (void)foo; 107 | 108 | // 方法调用 [[UIScreen mainScreen] respondsToSelector:@selector(scale)] 109 | case methodCallStart // 方法调用开始 110 | 111 | // @ 112 | case at // @ 113 | case atImplementation // @implementation 114 | case atProtocol // @protocol 115 | case atInterface // @interface 116 | case atInterfaceName // @interface name 117 | case atInterfaceParent // @interface name : base 118 | case atInterfaceContent // @interface 里内容 119 | case atProperty // @property 120 | case atPropertyDesStart // @property( 121 | case atPropertyDesEnd // @property(nonatomic, weak, readonly) 122 | case atPropertyTypeEnd // @property(nonatomic, weak, readonly) NSString 123 | 124 | // # 125 | case numberSign // # 126 | 127 | case normalBlock // oc方法外部的 block {},用于 c方法 128 | } 129 | 130 | // MARK: 解析 131 | public func parseNode(parentNode:OCNode, nodes:[OCTokenNode]) -> OCNode { 132 | var pNode = parentNode 133 | var currentState: RState = .normal 134 | //var currentLevel = 0 135 | //var recusiveNodeArr = [OCTokenNode]() 136 | var currentStartLine = 0 137 | var currentPairCount = 0 138 | 139 | // interface 140 | var currentInterfaceName = "" 141 | var currentInterfaceParent = "" 142 | 143 | // property 144 | var currentProperties = [OCProperty]() 145 | 146 | // method 147 | var currentMethodName = "" 148 | var currentClassName = "" 149 | var currentMethodReturnType = "" 150 | var currentMethodParamTypes = [String]() 151 | 152 | // method content 153 | var currentMethodTokenNodes = [OCTokenNode]() 154 | 155 | for tkNode in nodes { 156 | // MARK:方法 157 | if currentState == .methodShouldEnd { 158 | if tkNode.value == "{" { 159 | currentState = .methodContentStart 160 | currentPairCount += 1 161 | continue 162 | } else if (tkNode.type == .eod || tkNode.value == ";") { 163 | // 如果文件是头文件,方法定义处理不一样 164 | if inputFilePath.hasSuffix(".h") { 165 | currentState = .normal 166 | } 167 | } else { 168 | currentState = .normal 169 | } 170 | continue 171 | } 172 | 173 | if currentState == .methodContentStart { 174 | // 175 | currentMethodTokenNodes.append(tkNode) 176 | if tkNode.value == "{" { 177 | currentPairCount += 1 178 | continue 179 | } 180 | if tkNode.value == "}" { 181 | currentPairCount -= 1 182 | if currentPairCount == 0 { 183 | // method 的内容结束 184 | // 获取 method 代码 185 | var sourceContent = "" 186 | for i in currentStartLine.. 0 { 410 | // 当有基类时,需要做记录 411 | if currentInterfaceParent.count > 0 { 412 | OCStatistics.classAndBaseClass(aClass: currentInterfaceName, baseClass: currentInterfaceParent) 413 | } 414 | 415 | 416 | let nodeClass = OCNodeClass(className: currentInterfaceName, baseClass: currentInterfaceParent, hMethod: [String](), mMethod: [String](), baseClasses: [String](), properties: currentProperties) 417 | pNode.subNodes.append(OCNode(type: .class, subNodes: [OCNode](), identifier:"\(currentInterfaceName)", lineRange: (0, 0), source: "", value: nodeClass)) 418 | 419 | currentInterfaceName = "" 420 | currentInterfaceParent = "" 421 | currentProperties = [OCProperty]() 422 | } 423 | continue 424 | } 425 | 426 | // 其它情况比如 @synthesize 的处理 427 | currentState = .normal 428 | continue 429 | } 430 | 431 | // # 432 | if currentState == .numberSign { 433 | if tkNode.type == .eod { 434 | currentState = .normal 435 | } 436 | continue 437 | } 438 | 439 | // oc方法外部的 block {} 440 | if currentState == .normalBlock { 441 | currentMethodTokenNodes.append(tkNode) 442 | if tkNode.value == "{" { 443 | currentPairCount += 1 444 | continue 445 | } 446 | if tkNode.value == "}" { 447 | currentPairCount -= 1 448 | if currentPairCount == 0 { 449 | currentState = .normal 450 | 451 | // 当作匿名方法记录 452 | // 获取 block 代码 453 | var sourceContent = "" 454 | for i in currentStartLine.. OCNode { 517 | return OCNode(type: .default, subNodes: [OCNode](), identifier: "", lineRange: (0, 0), source: "", value: OCNodeDefaultValue()) 518 | } 519 | 520 | } 521 | --------------------------------------------------------------------------------