├── 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 |
--------------------------------------------------------------------------------