├── Podfile
├── SMCheckProject.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── project.pbxproj
├── SMCheckProject
├── ParsingImplementation.swift
├── AppDelegate.swift
├── ParsingMacro.swift
├── ParsingProtocol.swift
├── Info.plist
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Sb.swift
├── ParsingProperty.swift
├── ParsingImport.swift
├── DragView.swift
├── ParsingInterface.swift
├── ParsingMethod.swift
├── ParsingMethodContent.swift
├── CleanUnusedImports.swift
├── File.swift
├── ViewController.swift
├── ParsingBase.swift
├── CleanUnusedMethods.swift
└── Base.lproj
│ └── Main.storyboard
├── LICENSE
├── .gitignore
└── README.md
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 | use_frameworks!
3 | target 'SMCheckProject' do
4 | pod 'SnapKit', '~> 3.0.2'
5 | pod 'RxSwift', '~> 3.0'
6 | pod 'RxCocoa', '~> 3.0'
7 | end
8 |
--------------------------------------------------------------------------------
/SMCheckProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingImplementation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingImplementation.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2017/2/28.
6 | // Copyright © 2017年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ParsingImplementation: NSObject {
12 | class func parsing(line:String, inObject:Object) {
13 | // let aline = line.replacingOccurrences(of: Sb.atImplementation, with: "")
14 | // let tokens = ParsingBase.createOCLines(content: aline);
15 | // for tk in tokens {
16 | //
17 | // }
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/SMCheckProject/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2016/10/13.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 |
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | // Insert code here to initialize your application
18 | }
19 |
20 | func applicationWillTerminate(_ aNotification: Notification) {
21 | // Insert code here to tear down your application
22 | }
23 |
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingMacro.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingMacro.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2017/2/22.
6 | // Copyright © 2017年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ParsingMacro: NSObject {
12 | class func parsing(line:String) -> Macro {
13 | var macro = Macro()
14 | let aLine = line.replacingOccurrences(of: Sb.defineStr, with: "")
15 | let tokens = ParsingBase.createOCTokens(conent: aLine)
16 | guard let name = tokens.first else {
17 | return macro
18 | }
19 | macro.name = name
20 | macro.tokens = tokens
21 | return macro
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingProtocol.swift
3 | // SMCheckProject
4 | //
5 | // Created by didi on 2017/2/28.
6 | // Copyright © 2017年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ParsingProtocol: NSObject {
12 | //获取Protocol的name
13 | class func parsingNameFrom(line:String) -> String {
14 | guard let name = self.tokensFrom(line: line).first else {
15 | return ""
16 | }
17 | return name
18 | }
19 |
20 | class func tokensFrom(line:String) -> [String] {
21 | let aLine = line.replacingOccurrences(of: Sb.atProtocol, with: "")
22 | return ParsingBase.createOCTokens(conent: aLine)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 戴铭
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/SMCheckProject/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
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 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2016年 Starming. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/SMCheckProject/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 | }
--------------------------------------------------------------------------------
/SMCheckProject/Sb.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Sb.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2016/10/25.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class Sb: NSObject {
12 | public static let add = "+"
13 | public static let minus = "-"
14 | public static let rBktL = "("
15 | public static let rBktR = ")"
16 | public static let asterisk = "*"
17 | public static let colon = ":"
18 | public static let comma = ","
19 | public static let semicolon = ";"
20 | public static let divide = "/"
21 | public static let agBktL = "<"
22 | public static let agBktR = ">"
23 | public static let quotM = "\""
24 | public static let pSign = "#"
25 | public static let braceL = "{"
26 | public static let braceR = "}"
27 | public static let bktL = "["
28 | public static let bktR = "]"
29 | public static let qM = "?"
30 | public static let upArrow = "^"
31 |
32 | public static let atInteface = "@interface"
33 | public static let atImplementation = "@implementation"
34 | public static let atEnd = "@end"
35 | public static let atProtocol = "@protocol"
36 | public static let atOptional = "@optional"
37 | public static let atRequired = "@required"
38 |
39 | public static let atSelector = "@selector"
40 | public static let atProperty = "@property"
41 |
42 | public static let defineStr = "#define"
43 | public static let importStr = "#import"
44 |
45 | public static let space = " "
46 | public static let newLine = "\n"
47 | }
48 |
--------------------------------------------------------------------------------
/.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 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 | Pods/
48 | SMCheckProject.xcworkspace/
49 | Podfile.lock
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://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
64 |
65 | fastlane/report.xml
66 | fastlane/Preview.html
67 | fastlane/screenshots
68 | fastlane/test_output
69 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingProperty.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingProperty.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2017/2/23.
6 | // Copyright © 2017年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ParsingProperty: NSObject {
12 | class func parsing(tokens:Array) -> Property {
13 | var aProperty = Property()
14 | var inBktTf = false
15 | var inTypeTf = false
16 | var inNameTf = false
17 | var setName = ""
18 |
19 | for tk in tokens {
20 | //处理设置set
21 | if tk == Sb.rBktL && !inBktTf {
22 | inBktTf = true
23 | } else if tk == Sb.comma && inBktTf {
24 | aProperty.sets.append(setName)
25 | } else if tk == Sb.rBktR && inBktTf {
26 | aProperty.sets.append(setName)
27 | inBktTf = false
28 | setName = ""
29 | inTypeTf = true
30 | continue
31 | } else if inBktTf {
32 | setName = tk
33 | }
34 | //处理类型和属性名
35 | if inTypeTf && !inNameTf && (tk != Sb.agBktL || tk != Sb.agBktR) {
36 | aProperty.type = tk
37 | inNameTf = true
38 | continue
39 | }
40 | if inTypeTf && tk == Sb.agBktL {
41 | inNameTf = false
42 | }
43 | if inTypeTf && tk == Sb.agBktR {
44 | inNameTf = true
45 | continue
46 | }
47 | if inNameTf && tk != Sb.asterisk && tk != Sb.semicolon{
48 | aProperty.name = tk
49 | }
50 |
51 | }
52 |
53 | return aProperty
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingImport.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingImport.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2017/2/22.
6 | // Copyright © 2017年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ParsingImport: NSObject {
12 | class func parsing(tokens:Array) -> Import {
13 | var aImport = Import()
14 | //处理#import "file.h"
15 | var inQuot = false
16 | var fileName = ""
17 |
18 | //处理#import
19 | var inBkt = false
20 | var getLibNameTf = false
21 | var libName = ""
22 |
23 | for tk in tokens {
24 | //引号
25 | if tk == Sb.quotM && !inQuot {
26 | inQuot = true
27 | } else if tk == Sb.quotM && inQuot {
28 | inQuot = false
29 | aImport.fileName = fileName
30 | } else if inQuot {
31 | fileName += tk
32 | }
33 | //中括号
34 | if tk == Sb.agBktL && !inBkt {
35 | inBkt = true
36 | } else if tk == Sb.divide && inBkt && !getLibNameTf {
37 | aImport.libName = libName
38 | getLibNameTf = true
39 | } else if tk == Sb.agBktR && getLibNameTf {
40 | aImport.fileName = fileName
41 | //clean
42 | inBkt = false
43 | getLibNameTf = false
44 | fileName = ""
45 | libName = ""
46 | } else if inBkt && getLibNameTf {
47 | fileName += tk
48 | } else if inBkt && !getLibNameTf {
49 | libName += tk
50 | }
51 |
52 | }
53 |
54 | return aImport
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/SMCheckProject/DragView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DragView.swift
3 | // SMCheckProject
4 | //
5 | // Created by didi on 2016/11/1.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | protocol DragViewDelegate {
12 | func dragEnter();
13 | func dragExit();
14 | func dragFileOk(filePath:String);
15 | }
16 |
17 | class DragView: NSView {
18 | var delegate : DragViewDelegate?
19 |
20 | override func awakeFromNib() {
21 | super.awakeFromNib()
22 | self.register(forDraggedTypes: [NSFilenamesPboardType, NSURLPboardType, NSPasteboardTypeTIFF])
23 | }
24 |
25 | override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
26 | if let delegate = self.delegate {
27 | delegate.dragEnter()
28 | }
29 | return NSDragOperation.generic
30 | }
31 |
32 | override func draggingExited(_ sender: NSDraggingInfo?) {
33 | if let delegate = self.delegate {
34 | delegate.dragExit()
35 | }
36 | }
37 |
38 | override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
39 | return true
40 | }
41 |
42 | override func performDragOperation(_ sender: NSDraggingInfo) -> Bool {
43 | // var files = [URL]()
44 | var filePath = ""
45 | if let board = sender.draggingPasteboard().propertyList(forType: NSFilenamesPboardType) as? NSArray {
46 | for path in board {
47 | filePath = path as! String
48 | }
49 | }
50 | if let delegate = self.delegate {
51 | delegate.dragFileOk(filePath: filePath)
52 | }
53 | return true
54 | }
55 |
56 | override func draw(_ dirtyRect: NSRect) {
57 | super.draw(dirtyRect)
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingInterface.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingInterface.swift
3 | // SMCheckProject
4 | //
5 | // Created by didi on 2017/2/23.
6 | // Copyright © 2017年 Starming. All rights reserved.
7 | //
8 |
9 | /*
10 | 语法范例
11 | #@interface SMService (Car)
12 | #@interface SMPickView : UIView
13 | #@interface SMView ()
14 | */
15 |
16 | import Cocoa
17 |
18 | class ParsingInterface: NSObject {
19 | //将完整获取信息结合到object里
20 | class func parsing(line:String, inObject:Object) {
21 | let aLine = line.replacingOccurrences(of: Sb.atInteface, with: "")
22 | let tokens = ParsingBase.createOCTokens(conent: aLine)
23 |
24 | var endNameTf = false //完成类名获取
25 | var inSuperNameTf = false //获取:符号进入获取父类名阶段
26 | var inCategoryTf = false //获取类别
27 | var inProtocolTf = false //获取协议定义
28 |
29 | for tk in tokens {
30 | if !endNameTf && !inSuperNameTf && !inCategoryTf && !inProtocolTf {
31 | inObject.name = tk
32 | endNameTf = true
33 | } else if tk == Sb.colon && endNameTf && !inSuperNameTf && !inCategoryTf && !inProtocolTf {
34 | inSuperNameTf = true
35 | } else if tk == Sb.rBktL && endNameTf && !inCategoryTf && !inProtocolTf {
36 | inCategoryTf = true
37 | } else if inSuperNameTf && !inCategoryTf && !inProtocolTf {
38 | inObject.superName = tk
39 | inSuperNameTf = false
40 | } else if inCategoryTf && tk != Sb.rBktR && !inProtocolTf {
41 | inObject.category = tk
42 | } else if tk == Sb.agBktL && !inProtocolTf {
43 | inProtocolTf = true
44 | } else if tk != Sb.comma && tk != Sb.agBktR && inProtocolTf {
45 | inObject.usingProtocols.append(tk)
46 | }
47 | }
48 |
49 | }
50 | //获取interface的name
51 | class func parsingNameFrom(line:String) -> String {
52 | guard let name = self.tokensFrom(line: line).first else {
53 | return ""
54 | }
55 | return name
56 | }
57 |
58 | class func tokensFrom(line:String) -> [String] {
59 | let aLine = line.replacingOccurrences(of: Sb.atInteface, with: "")
60 | return ParsingBase.createOCTokens(conent: aLine)
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingMethod.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2016/10/20.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ParsingMethod: NSObject {
12 | class func parsing(tokens:Array) -> Method {
13 | var mtd = Method()
14 | var returnTypeTf = false //是否取得返回类型
15 | var parsingTf = false //解析中
16 | var bracketCount = 0 //括弧计数
17 | var step = 0 //1获取参数名,2获取参数类型,3获取iName
18 | var types = [String]()
19 | var methodParam = MethodParam()
20 | //print("\(arr)")
21 | for var tk in tokens {
22 | tk = tk.replacingOccurrences(of: Sb.newLine, with: "")
23 | if (tk == Sb.semicolon || tk == Sb.braceL) && step != 1 {
24 | var shouldAdd = false
25 |
26 | if mtd.params.count > 1 {
27 | //处理这种- (void)initWithC:(type)m m2:(type2)i, ... NS_REQUIRES_NIL_TERMINATION;入参为多参数情况
28 | if methodParam.type.characters.count > 0 {
29 | shouldAdd = true
30 | }
31 | } else {
32 | shouldAdd = true
33 | }
34 | if shouldAdd {
35 | mtd.params.append(methodParam)
36 | mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")
37 | }
38 |
39 | } else if tk == Sb.rBktL {
40 | bracketCount += 1
41 | parsingTf = true
42 | } else if tk == Sb.rBktR {
43 | bracketCount -= 1
44 | if bracketCount == 0 {
45 | var typeString = ""
46 | for typeTk in types {
47 | typeString = typeString.appending(typeTk)
48 | }
49 | if !returnTypeTf {
50 | //完成获取返回
51 | mtd.returnType = typeString
52 | step = 1
53 | returnTypeTf = true
54 | } else {
55 | if step == 2 {
56 | methodParam.type = typeString
57 | step = 3
58 | }
59 |
60 | }
61 | //括弧结束后的重置工作
62 | parsingTf = false
63 | types = []
64 | }
65 | } else if parsingTf {
66 | types.append(tk)
67 | //todo:返回block类型会使用.设置值的方式,目前获取用过方法方式没有.这种的解析,暂时作为
68 | if tk == Sb.upArrow {
69 | mtd.returnTypeBlockTf = true
70 | }
71 | } else if tk == Sb.colon {
72 | step = 2
73 | } else if step == 1 {
74 | if tk == "initWithCoordinate" {
75 | //
76 | }
77 | methodParam.name = tk
78 | step = 0
79 | } else if step == 3 {
80 | methodParam.iName = tk
81 | step = 1
82 | mtd.params.append(methodParam)
83 | mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")
84 | methodParam = MethodParam()
85 | } else if tk != Sb.minus && tk != Sb.add {
86 | methodParam.name = tk
87 | }
88 |
89 | }//遍历
90 |
91 | return mtd
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingMethodContent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingMethodContent.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2016/10/23.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import RxSwift
11 |
12 | class ParsingMethodContent: NSObject {
13 | class func parsing(method:Method, file:File) -> Observable {
14 | return Observable.create({ (observer) -> Disposable in
15 | for tk in method.tokens {
16 | guard let obj = file.importObjects[tk] else {
17 | continue
18 | }
19 | guard let _ = file.usedObjects[tk] else {
20 | //记录使用过的类
21 | file.usedObjects[tk] = obj
22 | observer.on(.next(obj))
23 |
24 | continue
25 | }
26 | }
27 |
28 | observer.on(.completed)
29 | return Disposables.create {
30 |
31 | }
32 | })
33 |
34 | }
35 |
36 | //-----------处理用过的方法------------
37 | class func parsing(contentArr:Array, inMethod:Method) -> Method {
38 | var mtdIn = inMethod
39 | mtdIn.tokens = contentArr
40 |
41 | //todo:还要过滤@""这种情况
42 | var psBrcStep = 0
43 | var uMtdDic = [Int:Method]()
44 | var preTk = ""
45 | //处理?:这种条件判断简写方式
46 | var psCdtTf = false
47 | var psCdtStep = 0
48 | //判断selector
49 | var psSelectorTf = false
50 | var preSelectorTk = ""
51 | var selectorMtd = Method()
52 | var selectorMtdPar = MethodParam()
53 |
54 | uMtdDic[psBrcStep] = Method() //初始时就实例化一个method,避免在define里定义只定义]符号
55 |
56 | for var tk in contentArr {
57 | //selector处理
58 | if psSelectorTf {
59 | if tk == Sb.colon {
60 | selectorMtdPar.name = preSelectorTk
61 | selectorMtd.params.append(selectorMtdPar)
62 | selectorMtd.pnameId += "\(selectorMtdPar.name):"
63 | } else if tk == Sb.rBktR {
64 | mtdIn.usedMethod.append(selectorMtd)
65 | psSelectorTf = false
66 | selectorMtd = Method()
67 | selectorMtdPar = MethodParam()
68 | } else {
69 | preSelectorTk = tk
70 | }
71 | continue
72 | }
73 | if tk == Sb.atSelector {
74 | psSelectorTf = true
75 | selectorMtd = Method()
76 | selectorMtdPar = MethodParam()
77 | continue
78 | }
79 | //通常处理
80 | if tk == Sb.bktL {
81 | if psCdtTf {
82 | psCdtStep += 1
83 | }
84 | psBrcStep += 1
85 | uMtdDic[psBrcStep] = Method()
86 | } else if tk == Sb.bktR {
87 | if psCdtTf {
88 | psCdtStep -= 1
89 | }
90 | if (uMtdDic[psBrcStep]?.params.count)! > 0 {
91 | mtdIn.usedMethod.append(uMtdDic[psBrcStep]!)
92 | }
93 | psBrcStep -= 1
94 | //[]不配对的容错处理
95 | if psBrcStep < 0 {
96 | psBrcStep = 0
97 | }
98 |
99 | } else if tk == Sb.colon {
100 | //条件简写情况处理
101 | if psCdtTf && psCdtStep == 0 {
102 | psCdtTf = false
103 | continue
104 | }
105 | //dictionary情况处理@"key":@"value"
106 | if preTk == Sb.quotM || preTk == "respondsToSelector" {
107 | continue
108 | }
109 | let prm = MethodParam()
110 | prm.name = preTk
111 | if prm.name != "" {
112 | uMtdDic[psBrcStep]?.params.append(prm)
113 | uMtdDic[psBrcStep]?.pnameId = (uMtdDic[psBrcStep]?.pnameId.appending("\(prm.name):"))!
114 | }
115 | } else if tk == Sb.qM {
116 | psCdtTf = true
117 | } else {
118 | tk = tk.replacingOccurrences(of: Sb.newLine, with: "")
119 | preTk = tk
120 | }
121 | }
122 |
123 | return mtdIn
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/SMCheckProject/CleanUnusedImports.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CleanUnusedImports.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2017/3/1.
6 | // Copyright © 2017年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import RxSwift
11 |
12 | //获取递归后所有的import
13 | class CleanUnusedImports: NSObject {
14 | func find(files:Dictionary) -> Observable {
15 |
16 | return Observable.create({ (observer) -> Disposable in
17 |
18 | var allFiles = [String:File]() //需要查找的头文件
19 | var newFiles = [String:File]() //递归全的文件
20 |
21 | var allObjects = [String:Object]()
22 | var allUsedObjects = [String:Object]()
23 |
24 | for (_, aFile) in files {
25 | allFiles[aFile.name] = aFile
26 | }
27 | for (_, aFile) in allFiles {
28 | //单文件处理
29 | aFile.recursionImports = self.fetchImports(file: aFile, allFiles: allFiles, allRecursionImports:[Import]())
30 | //记录所有import的的类
31 | for aImport in aFile.recursionImports {
32 | for (name, aObj) in aImport.file.objects {
33 | //全部类
34 | aFile.importObjects[name] = aObj
35 | allObjects[name] = aObj
36 | }
37 | }
38 | newFiles[aFile.name] = aFile
39 | //处理无用的import
40 | //记录所有用过的类
41 | for aMethod in aFile.methods {
42 | let _ = ParsingMethodContent.parsing(method: aMethod, file: aFile).subscribe(onNext:{ (result) in
43 | if result is Object {
44 | let aObj = result as! Object
45 | allUsedObjects[aObj.name] = aObj
46 | }
47 | })
48 | }
49 | //记录类的父类,作为已用类
50 | for (_, value) in allObjects {
51 | if value.superName.characters.count > 0 {
52 | guard let obj = allObjects[value.superName] else {
53 | continue
54 | }
55 | allUsedObjects[value.superName] = obj
56 | }
57 | }
58 |
59 | }
60 | // print("\(allObjects.keys)")
61 | // print("-----------------------")
62 | // print("\(allUsedObjects.keys)")
63 | //遍历对比出无用的类
64 | var allUnUsedObjects = [String:Object]()
65 | for (key, value) in allObjects {
66 | guard let _ = allUsedObjects[key] else {
67 | allUnUsedObjects[key] = value
68 | continue
69 | }
70 | }
71 | observer.on(.next(allUnUsedObjects))
72 | observer.on(.next(newFiles))
73 | observer.on(.completed)
74 | return Disposables.create {
75 | //
76 | }
77 | })
78 | }
79 |
80 | //递归获取所有import
81 | func fetchImports(file: File, allFiles:[String:File], allRecursionImports:[Import]) -> [Import] {
82 | var allRecursionImports = allRecursionImports
83 | for aImport in file.imports {
84 |
85 |
86 | guard let importFile = allFiles[aImport.fileName] else {
87 | continue
88 | }
89 | if !checkIfContain(aImport: aImport, inImports: allRecursionImports) {
90 | allRecursionImports.append(addFileObjectTo(aImport: aImport, allFiles: allFiles))
91 | } else {
92 | continue
93 | }
94 |
95 | let reRecursionImports = fetchImports(file: importFile, allFiles: allFiles, allRecursionImports: allRecursionImports)
96 | for aImport in reRecursionImports {
97 | if !checkIfContain(aImport: aImport, inImports: allRecursionImports) {
98 | allRecursionImports.append(addFileObjectTo(aImport: aImport, allFiles: allFiles))
99 | }
100 | }
101 |
102 | }
103 | return allRecursionImports
104 | }
105 |
106 | func addFileObjectTo(aImport:Import, allFiles: [String:File]) -> Import {
107 | var mImport = aImport
108 | guard let aFile = allFiles[aImport.fileName] else {
109 | return aImport
110 | }
111 | mImport.file = aFile
112 | return mImport
113 | }
114 |
115 | func checkIfContain(aImport:Import, inImports:[Import]) -> Bool{
116 | let tf = inImports.contains { element in
117 | if aImport.fileName == element.fileName {
118 | return true
119 | } else {
120 | return false
121 | }
122 | }
123 | return tf
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/SMCheckProject/File.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2016/10/20.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | enum FileType {
11 | case FileH,FileM,FileSwift
12 | }
13 | //文件
14 | class File: NSObject {
15 | public var path = "" {
16 | didSet {
17 | if path.hasSuffix(".h") {
18 | type = FileType.FileH
19 | } else if path.hasSuffix(".m") {
20 | type = FileType.FileM
21 | } else if path.hasSuffix(".swift") {
22 | type = FileType.FileSwift
23 | }
24 | name = (path.components(separatedBy: "/").last)!
25 | }
26 | }
27 | public var type = FileType.FileH
28 | public var name = ""
29 | public var content = ""
30 | public var methods = [Method]() //所有方法
31 | public var imports = [Import]() //一级引入
32 | public var recursionImports = [Import]() //递归所有层级引入
33 | public var importObjects = [String:Object]() //所有引入的对象
34 | public var usedObjects = [String:Object]() //已经使用过的对象,无用的引入可以通过对比self.imports里的文件里的定义的objects求差集得到
35 | public var objects = [String:Object]() //文件里定义的所有类
36 | public var macros = [String:Macro]() //文件里定义的宏,全局的也会有一份
37 | public var protocols = [String:Protocol]() //Todo:还没用,作为性能提升用
38 |
39 |
40 | func des() -> String {
41 | var str = ""
42 | str += "文件路径:\(path)\n"
43 | str += "文件名:\(name)\n"
44 | str += "方法数量:\(methods.count)\n"
45 | str += "方法列表:\n"
46 | for aMethod in methods {
47 | var showStr = "- (\(aMethod.returnType)) "
48 | showStr = showStr.appending(File.desDefineMethodParams(paramArr: aMethod.params))
49 | str += "\n\(showStr)\n"
50 | if aMethod.usedMethod.count > 0 {
51 | str += "用过的方法----------\n"
52 | showStr = ""
53 | for aUsedMethod in aMethod.usedMethod {
54 | showStr = ""
55 | showStr = showStr.appending(File.desUsedMethodParams(paramArr: aUsedMethod.params))
56 | str += "\(showStr)\n"
57 | }
58 | str += "------------------\n"
59 | }
60 |
61 | }
62 | //Todo: 添加更多详细的文件里的信息。比如说object里的和新加的marcos等。
63 | return str
64 | }
65 |
66 | //类方法
67 | //打印定义方法参数
68 | class func desDefineMethodParams(paramArr:[MethodParam]) -> String {
69 | var showStr = ""
70 | for aParam in paramArr {
71 | if aParam.type == "" {
72 | showStr = showStr.appending("\(aParam.name);")
73 | } else {
74 | showStr = showStr.appending("\(aParam.name):(\(aParam.type))\(aParam.iName);")
75 | }
76 |
77 | }
78 | return showStr
79 | }
80 | class func desUsedMethodParams(paramArr:[MethodParam]) -> String {
81 | var showStr = ""
82 | for aUParam in paramArr {
83 | showStr = showStr.appending("\(aUParam.name):")
84 | }
85 | return showStr
86 | }
87 | }
88 | //#import "file.h"
89 | struct Import {
90 | public var fileName = ""
91 | public var libName = ""
92 | public var file = File() //这样记录所有递归出的引用类时就能够直接获取File里的详细信息了
93 | }
94 |
95 | //#define REDCOLOR [UIColor HexString:@"000000"]
96 | struct Macro {
97 | public var name = ""
98 | public var tokens = [String]()
99 | }
100 |
101 | //@protocol DCOrderListViewDelegate
102 | struct Protocol {
103 | public var name = ""
104 | public var methods = [Method]()
105 | }
106 |
107 | //对象
108 | class Object {
109 | public var name = ""
110 | public var superName = ""
111 | public var category = ""
112 | public var usingProtocols = [String]() //协议
113 | public var properties = [Property]() //对象里定义的属性
114 | public var methods = [Method]() //定义的方法
115 | public var protocols = [String:Protocol]() //Todo:还没用,根据属性和来看看那些属性地方会用什么protocol
116 | }
117 |
118 | struct Property {
119 | public var name = ""
120 | public var type = ""
121 | public var sets = [String]() //nonatomic strong
122 | }
123 |
124 | struct Method {
125 | public var classMethodTf = false //+ or -
126 | public var returnType = ""
127 | public var returnTypePointTf = false
128 | public var returnTypeBlockTf = false
129 | public var params = [MethodParam]()
130 | public var tokens = [String]() //方法内容token
131 | public var usedMethod = [Method]()
132 | public var tmpObjects = [Object]() //临时变量集
133 | public var filePath = "" //定义方法的文件路径,方便修改文件使用
134 | public var pnameId = "" //唯一标识,便于快速比较
135 |
136 | }
137 |
138 | class MethodParam: NSObject {
139 | public var name = ""
140 | public var type = ""
141 | public var typePointTf = false //是否是指针类型
142 | public var iName = ""
143 | }
144 |
145 | class Type: NSObject {
146 | //todo:更多类型
147 | public var name = ""
148 | public var type = 0 //0是值类型 1是指针
149 | }
150 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/SMCheckProject/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2016/10/13.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import SnapKit
11 | import RxSwift
12 |
13 | class ViewController: NSViewController,NSTableViewDataSource,NSTableViewDelegate,DragViewDelegate {
14 |
15 | @IBOutlet weak var parsingIndicator: NSProgressIndicator!
16 | @IBOutlet weak var desLb: NSTextField!
17 | @IBOutlet weak var pathDes: NSTextField!
18 | @IBOutlet weak var cleanBt: NSButton!
19 | @IBOutlet weak var resultTb: NSTableView!
20 | @IBOutlet weak var dragView: DragView!
21 | @IBOutlet weak var seachBt: NSButtonCell!
22 | @IBOutlet weak var detailTv: NSScrollView!
23 | @IBOutlet var detailTxv: NSTextView!
24 |
25 | var unusedMethods = [Method]() //无用方法
26 | var selectedPath : String = "" {
27 | didSet {
28 | if selectedPath.characters.count > 0 {
29 | let ud = UserDefaults()
30 | ud.set(selectedPath, forKey: "selectedPath")
31 | ud.synchronize()
32 | pathDes.stringValue = selectedPath.replacingOccurrences(of: "file://", with: "")
33 | }
34 | }
35 | }
36 | var filesDic = [String:File]() //遍历后文件集
37 | var parsingLog = "" //遍历后的日志
38 |
39 | override func viewDidLoad() {
40 | super.viewDidLoad()
41 | detailTxv.string = ""
42 | detailTxv.textColor = NSColor.gray
43 | parsingIndicator.isHidden = true
44 | desLb.stringValue = ""
45 | pathDes.stringValue = "请选择工程目录"
46 | cleanBt.isEnabled = false
47 | resultTb.doubleAction = #selector(cellDoubleClick)
48 | if (UserDefaults().object(forKey: "selectedPath") != nil) {
49 | selectedPath = UserDefaults().value(forKey: "selectedPath") as! String
50 | }
51 |
52 | }
53 |
54 | override func awakeFromNib() {
55 | dragView.delegate = self
56 | }
57 |
58 | //查找按钮
59 | @IBAction func searchMethodAction(_ sender: Any) {
60 | if selectedPath.characters.count > 0 {
61 | self.searchingUnusedMethods()
62 | }
63 |
64 | }
65 |
66 | //清理按钮
67 | @IBAction func cleanMethodsAction(_ sender: AnyObject) {
68 | desLb.stringValue = "清理中..."
69 | cleanBt.isEnabled = false
70 | parsingIndicator.startAnimation(nil)
71 | DispatchQueue.global().async {
72 | CleanUnusedMethods().clean(methods: self.unusedMethods)
73 | DispatchQueue.main.async {
74 | self.cleanBt.isEnabled = true
75 | self.parsingIndicator.stopAnimation(nil)
76 | self.parsingIndicator.isHidden = true
77 | self.desLb.stringValue = "完成清理"
78 | self.detailTxv.string = ""
79 | }
80 | }
81 | }
82 |
83 | //Priavte
84 | private func searchingUnusedMethods() {
85 | parsingIndicator.isHidden = false
86 | parsingIndicator.startAnimation(nil)
87 | desLb.stringValue = "查找中..."
88 | cleanBt.isEnabled = false
89 | seachBt.isEnabled = false
90 | pathDes.stringValue = selectedPath.replacingOccurrences(of: "file://", with: "")
91 | detailTxv.string = ""
92 | parsingLog = ""
93 | DispatchQueue.global().async {
94 | // self.unusedMethods = CleanUnusedMethods().find(path: self.selectedPath)
95 | _ = CleanUnusedMethods().find(path: self.selectedPath).subscribe(onNext: { (result) in
96 | if result is String {
97 | DispatchQueue.main.async {
98 | self.desLb.stringValue = result as! String
99 | }
100 |
101 | } else if result is [Method] {
102 | self.unusedMethods = result as! [Method]
103 | } else if result is File {
104 | DispatchQueue.main.async {
105 | let aFile = result as! File
106 | self.filesDic[aFile.path] = aFile
107 | self.parsingLog = self.parsingLog + aFile.des() + "\n"
108 |
109 | }
110 | }
111 | })
112 | DispatchQueue.main.async {
113 | self.cleanBt.isEnabled = true
114 | self.seachBt.isEnabled = true
115 | self.parsingIndicator.stopAnimation(nil)
116 | self.parsingIndicator.isHidden = true
117 | self.desLb.stringValue = "完成查找"
118 | self.resultTb.reloadData()
119 |
120 | self.detailTxv.string = self.parsingLog
121 | self.detailTv.contentView .scroll(to: NSPoint(x: 0, y: ((self.detailTv.documentView?.frame.size.height)! - self.detailTv.contentSize.height)))
122 |
123 | //处理无用import
124 | DispatchQueue.global().async {
125 | let _ = CleanUnusedImports().find(files: self.filesDic).subscribe(onNext: { (result) in
126 | if result is [String:File] {
127 | //接受更新后的全部文件
128 | self.filesDic = result as! [String:File]
129 | } else if result is [String:Object] {
130 | //接受全部无用import字典
131 |
132 | }
133 | })
134 | }
135 |
136 | }
137 | }
138 |
139 | }
140 |
141 | //选择一个文件夹
142 | private func selectFolder() -> String {
143 | let openPanel = NSOpenPanel();
144 | openPanel.canChooseDirectories = true;
145 | openPanel.canChooseFiles = false;
146 | if(openPanel.runModal() == NSModalResponseOK) {
147 | //print(openPanel.url?.absoluteString)
148 | let path = openPanel.url?.absoluteString
149 | //print("选择文件夹路径: \(path)")
150 | return path!
151 | }
152 |
153 | return ""
154 | }
155 |
156 | //TableView
157 | //Cell DoubleClick
158 | func cellDoubleClick() {
159 | let aMethod = self.unusedMethods[self.resultTb.clickedRow]
160 | let filePathString = aMethod.filePath.replacingOccurrences(of: "file://", with: "")
161 | //双击打开finder到指定的文件
162 | NSWorkspace.shared().openFile(filePathString, withApplication: "Xcode")
163 | }
164 | //Cell OneClick
165 | func cellOneClick() {
166 | let aMethod = self.unusedMethods[self.resultTb.selectedRow]
167 | let cFile = self.filesDic[aMethod.filePath]
168 | self.detailTxv.string = cFile?.content
169 | }
170 |
171 | //NSTableViewDataSource
172 | func numberOfRows(in tableView: NSTableView) -> Int {
173 | return self.unusedMethods.count
174 | }
175 |
176 | //NSTableViewDelegate
177 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
178 | let aMethod = self.unusedMethods[row]
179 |
180 | let columnId = tableColumn?.identifier
181 | if columnId == "MethodId" {
182 | return aMethod.pnameId
183 | } else if columnId == "MethodPath" {
184 | let filePathString = aMethod.filePath.replacingOccurrences(of: "file://", with: "")
185 | return filePathString
186 | }
187 |
188 | return nil
189 | }
190 |
191 | func tableViewSelectionDidChange(_ notification: Notification) {
192 | self.cellOneClick()
193 | }
194 |
195 | //DragViewDelegate
196 | func dragExit() {
197 | //
198 | }
199 | func dragEnter() {
200 | //
201 | }
202 | func dragFileOk(filePath: String) {
203 | print("\(filePath)")
204 | selectedPath = "file://" + filePath + "/"
205 | pathDes.stringValue = selectedPath.replacingOccurrences(of: "file://", with: "")
206 | }
207 | }
208 |
209 |
--------------------------------------------------------------------------------
/SMCheckProject/ParsingBase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParsingBase.swift
3 | // SMCheckProject
4 | //
5 | // Created by daiming on 2016/10/20.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ParsingBase: NSObject {
12 | //删除指定的一组方法
13 | class func delete(methods:[Method]) {
14 | print("无用方法")
15 | for aMethod in methods {
16 | print("\(File.desDefineMethodParams(paramArr: aMethod.params))")
17 |
18 | //开始删除
19 | //continue
20 | var hContent = ""
21 | var mContent = ""
22 | var mFilePath = aMethod.filePath
23 | if aMethod.filePath.hasSuffix(".h") {
24 | hContent = try! String(contentsOf: URL(string:aMethod.filePath)!, encoding: String.Encoding.utf8)
25 | //todo:因为先处理了h文件的情况
26 | mFilePath = aMethod.filePath.trimmingCharacters(in: CharacterSet(charactersIn: "h")) //去除头尾字符集
27 | mFilePath = mFilePath.appending("m")
28 | }
29 | if mFilePath.hasSuffix(".m") {
30 | do {
31 | mContent = try String(contentsOf: URL(string:mFilePath)!, encoding: String.Encoding.utf8)
32 | } catch {
33 | mContent = ""
34 | }
35 |
36 | }
37 |
38 | let hContentArr = hContent.components(separatedBy: CharacterSet.newlines)
39 | let mContentArr = mContent.components(separatedBy: CharacterSet.newlines)
40 | //print(mContentArr)
41 | //----------------h文件------------------
42 | var psHMtdTf = false
43 | var hMtds = [String]()
44 | var hMtdStr = ""
45 | var hMtdAnnoStr = ""
46 | var hContentCleaned = ""
47 | for hOneLine in hContentArr {
48 | var line = hOneLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
49 |
50 | if line.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {
51 | psHMtdTf = true
52 | hMtds += self.createOCTokens(conent: line)
53 | hMtdStr = hMtdStr.appending(hOneLine + Sb.newLine)
54 | hMtdAnnoStr += "//-----由SMCheckProject工具删除-----\n//"
55 | hMtdAnnoStr += hOneLine + Sb.newLine
56 | line = self.dislodgeAnnotaionInOneLine(content: line)
57 | line = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
58 | } else if psHMtdTf {
59 | hMtds += self.createOCTokens(conent: line)
60 | hMtdStr = hMtdStr.appending(hOneLine + Sb.newLine)
61 | hMtdAnnoStr += "//" + hOneLine + Sb.newLine
62 | line = self.dislodgeAnnotaionInOneLine(content: line)
63 | line = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
64 | } else {
65 | hContentCleaned += hOneLine + Sb.newLine
66 | }
67 |
68 | if line.hasSuffix(Sb.semicolon) && psHMtdTf{
69 | psHMtdTf = false
70 |
71 | let methodPnameId = ParsingMethod.parsing(tokens: hMtds).pnameId
72 | if aMethod.pnameId == methodPnameId {
73 | hContentCleaned += hMtdAnnoStr
74 |
75 | } else {
76 | hContentCleaned += hMtdStr
77 | }
78 | hMtdAnnoStr = ""
79 | hMtdStr = ""
80 | hMtds = []
81 | }
82 |
83 |
84 | }
85 | //删除无用函数
86 | do {
87 | try hContentCleaned.write(to: URL(string:aMethod.filePath)!, atomically: false, encoding: String.Encoding.utf8)
88 | } catch {
89 | //
90 | }
91 |
92 |
93 | //----------------m文件----------------
94 | var mDeletingTf = false
95 | var mBraceCount = 0
96 | var mContentCleaned = ""
97 | var mMtdStr = ""
98 | var mMtdAnnoStr = ""
99 | var mMtds = [String]()
100 | var psMMtdTf = false
101 | for mOneLine in mContentArr {
102 | let line = mOneLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
103 |
104 | if mDeletingTf {
105 | let lTokens = self.createOCTokens(conent: line)
106 | mMtdAnnoStr += "//" + mOneLine + Sb.newLine
107 | for tk in lTokens {
108 | if tk == Sb.braceL {
109 | mBraceCount += 1
110 | }
111 | if tk == Sb.braceR {
112 | mBraceCount -= 1
113 | if mBraceCount == 0 {
114 | mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)
115 | mMtdAnnoStr = ""
116 | mDeletingTf = false
117 | }
118 | }
119 | }
120 |
121 | continue
122 | }
123 |
124 |
125 | if line.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {
126 | psMMtdTf = true
127 | mMtds += self.createOCTokens(conent: line)
128 | mMtdStr = mMtdStr.appending(mOneLine + Sb.newLine)
129 | mMtdAnnoStr += "//-----由SMCheckProject工具删除-----\n//" + mOneLine + Sb.newLine
130 | } else if psMMtdTf {
131 | mMtdStr = mMtdStr.appending(mOneLine + Sb.newLine)
132 | mMtdAnnoStr += "//" + mOneLine + Sb.newLine
133 | mMtds += self.createOCTokens(conent: line)
134 | } else {
135 | mContentCleaned = mContentCleaned.appending(mOneLine + Sb.newLine)
136 | }
137 |
138 | if line.hasSuffix(Sb.braceL) && psMMtdTf {
139 | psMMtdTf = false
140 | let methodPnameId = ParsingMethod.parsing(tokens: mMtds).pnameId
141 | if aMethod.pnameId == methodPnameId {
142 | mDeletingTf = true
143 | mBraceCount += 1
144 | mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)
145 | } else {
146 | mContentCleaned = mContentCleaned.appending(mMtdStr)
147 | }
148 | mMtdStr = ""
149 | mMtdAnnoStr = ""
150 | mMtds = []
151 | }
152 |
153 | } //m文件
154 |
155 | //删除无用函数
156 | if mContent.characters.count > 0 {
157 | do {
158 | try mContentCleaned.write(to: URL(string:mFilePath)!, atomically: false, encoding: String.Encoding.utf8)
159 | } catch {
160 | //
161 | }
162 |
163 | }
164 |
165 | }
166 | }
167 |
168 | //根据代码文件解析出一个根据行切分的数组
169 | class func createOCLines(content:String) -> [String] {
170 | var str = content
171 | str = self.dislodgeAnnotaion(content: str)
172 | let strArr = str.components(separatedBy: CharacterSet.newlines)
173 | return strArr
174 | }
175 |
176 | //根据代码文件解析出一个根据标记符切分的数组
177 | class func createOCTokens(conent:String) -> [String] {
178 | var str = conent
179 |
180 | str = self.dislodgeAnnotaion(content: str)
181 |
182 | //开始扫描切割
183 | let scanner = Scanner(string: str)
184 | var tokens = [String]()
185 | //Todo:待处理符号,.
186 | let operaters = [Sb.add,Sb.minus,Sb.rBktL,Sb.rBktR,Sb.asterisk,Sb.colon,Sb.comma,Sb.semicolon,Sb.divide,Sb.agBktL,Sb.agBktR,Sb.quotM,Sb.pSign,Sb.braceL,Sb.braceR,Sb.bktL,Sb.bktR,Sb.qM]
187 | var operatersString = ""
188 | for op in operaters {
189 | operatersString = operatersString.appending(op)
190 | }
191 |
192 | var set = CharacterSet()
193 | set.insert(charactersIn: operatersString)
194 | set.formUnion(CharacterSet.whitespacesAndNewlines)
195 |
196 | while !scanner.isAtEnd {
197 | for operater in operaters {
198 | if (scanner.scanString(operater, into: nil)) {
199 | tokens.append(operater)
200 | }
201 | }
202 |
203 | var result:NSString?
204 | result = nil
205 | if scanner.scanUpToCharacters(from: set, into: &result) {
206 | tokens.append(result as! String)
207 | }
208 | }
209 | tokens = tokens.filter {
210 | $0 != Sb.space
211 | }
212 | return tokens;
213 | }
214 |
215 | //清理注释
216 | class func dislodgeAnnotaion(content:String) -> String {
217 |
218 | let annotationBlockPattern = "/\\*[\\s\\S]*?\\*/" //匹配/*...*/这样的注释
219 | let annotationLinePattern = "//.*?\\n" //匹配//这样的注释
220 |
221 | let regexBlock = try! NSRegularExpression(pattern: annotationBlockPattern, options: NSRegularExpression.Options(rawValue:0))
222 | let regexLine = try! NSRegularExpression(pattern: annotationLinePattern, options: NSRegularExpression.Options(rawValue:0))
223 | var newStr = ""
224 | newStr = regexLine.stringByReplacingMatches(in: content, options: NSRegularExpression.MatchingOptions(rawValue:0), range: NSMakeRange(0, content.characters.count), withTemplate: Sb.space)
225 | newStr = regexBlock.stringByReplacingMatches(in: newStr, options: NSRegularExpression.MatchingOptions(rawValue:0), range: NSMakeRange(0, newStr.characters.count), withTemplate: Sb.space)
226 | return newStr
227 | }
228 | //一行内清理注释
229 | class func dislodgeAnnotaionInOneLine(content:String) -> String {
230 | let annotationLinePattern = "//[\\s\\S]*?$" //匹配//这样的注释
231 |
232 | let regexLine = try! NSRegularExpression(pattern: annotationLinePattern, options: NSRegularExpression.Options(rawValue:0))
233 | var newStr = ""
234 | newStr = regexLine.stringByReplacingMatches(in: content, options: NSRegularExpression.MatchingOptions(rawValue:0), range: NSMakeRange(0, content.characters.count), withTemplate: "")
235 | return newStr
236 | }
237 | }
238 |
--------------------------------------------------------------------------------
/SMCheckProject/CleanUnusedMethods.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CleanUnusedMethods.swift
3 | // SMCheckProject
4 | //
5 | // Created by didi on 2016/10/31.
6 | // Copyright © 2016年 Starming. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import RxSwift
11 |
12 | class CleanUnusedMethods: NSObject {
13 | override init() {
14 |
15 | }
16 |
17 | func find(path: String) -> Observable {
18 |
19 | return Observable.create({ (observer) -> Disposable in
20 | let fileFolderPath = path
21 | let fileFolderStringPath = fileFolderPath.replacingOccurrences(of: "file://", with: "")
22 | let fileManager = FileManager.default;
23 | //深度遍历
24 | let enumeratorAtPath = fileManager.enumerator(atPath: fileFolderStringPath)
25 | //过滤文件后缀
26 | let filterPath = NSArray(array: (enumeratorAtPath?.allObjects)!).pathsMatchingExtensions(["h","m"])
27 | // print("过滤后缀后的文件: \(filterPath)")
28 |
29 | var methodsDefinedInHFile = [Method]() //h文件定义的方法集合
30 | var methodsDefinedInMFile = [Method]() //m文件定义的方法集合
31 | var methodsMFile = [String]() //m文件pnameId集合
32 | var methodsUsed = [String]() //用过的方法集合
33 | var protocols = [String:Protocol]() //delegate
34 | var marcros = [String:Macro]() //宏
35 |
36 | //遍历文件夹下所有文件
37 | var i = 0
38 | let count = filterPath.count
39 | for filePathString in filterPath {
40 |
41 | var fullPath = fileFolderPath
42 |
43 | fullPath.append(filePathString)
44 |
45 | //读取文件内容
46 | let fileUrl = URL(string: fullPath)
47 |
48 | if fileUrl == nil {
49 |
50 | } else {
51 | let aFile = File()
52 | aFile.path = fullPath
53 | //显示parsing的状态
54 | observer.on(.next("进度:\(i+1)/\(count) 正在查询文件:\(aFile.name)"))
55 | i += 1
56 | let content = try! String(contentsOf: fileUrl!, encoding: String.Encoding.utf8)
57 | //print("文件内容: \(content)")
58 | aFile.content = content
59 |
60 | let tokens = ParsingBase.createOCTokens(conent: content)
61 |
62 | //----------根据行数切割----------
63 | let lines = ParsingBase.createOCLines(content: content)
64 | var inInterfaceTf = false
65 | var inImplementationTf = false
66 | var inProtocolTf = false
67 | var currentProtocolName = ""
68 | var obj = Object()
69 |
70 | for var aLine in lines {
71 | //清理头尾
72 | aLine = aLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
73 |
74 | let tokens = ParsingBase.createOCTokens(conent: aLine)
75 | //处理 #define start
76 | if aLine.hasPrefix(Sb.defineStr) {
77 | //处理宏定义的方法
78 | let reMethod = ParsingMethodContent.parsing(contentArr: tokens, inMethod: Method())
79 | if reMethod.usedMethod.count > 0 {
80 | for aUsedMethod in reMethod.usedMethod {
81 | //将用过的方法添加到集合中
82 | methodsUsed.append(aUsedMethod.pnameId)
83 | }
84 | }
85 | //保存在文件结构体中
86 | let aMarcro = ParsingMacro.parsing(line: aLine)
87 | aFile.macros[aMarcro.name] = aMarcro //添加到文件的集合里
88 | marcros[aMarcro.name] = aMarcro //添加到全局里
89 | continue
90 | }//#define end
91 |
92 | //处理 #import start
93 | if aLine.hasPrefix(Sb.importStr) {
94 | let imp = ParsingImport.parsing(tokens: tokens)
95 | guard imp.fileName.characters.count > 0 else {
96 | continue
97 | }
98 | aFile.imports.append(imp)
99 | continue
100 | }//#import end
101 |
102 |
103 | //处理 @interface
104 | if aLine.hasPrefix(Sb.atInteface) && !inInterfaceTf {
105 | inInterfaceTf = true
106 | inProtocolTf = false
107 | currentProtocolName = ""
108 |
109 | //查找文件中是否有该类,有就使用那个,没有就创建一个
110 | let objName = ParsingInterface.parsingNameFrom(line: aLine)
111 | if !aFile.objects.keys.contains(objName) {
112 | obj = Object()
113 | aFile.objects[objName] = obj
114 | }
115 |
116 | ParsingInterface.parsing(line: aLine, inObject: obj)
117 | continue
118 | }
119 | if inInterfaceTf {
120 | //处理属性
121 | if aLine.hasPrefix(Sb.atProperty) {
122 | let aProperty = ParsingProperty.parsing(tokens: ParsingBase.createOCTokens(conent: aLine))
123 | obj.properties.append(aProperty)
124 | }
125 | }
126 | if aLine.hasPrefix(Sb.atEnd) && inInterfaceTf {
127 | // aFile.objects.append(obj)
128 | inInterfaceTf = false
129 | inProtocolTf = false
130 | currentProtocolName = ""
131 | continue
132 | }
133 |
134 | //处理 @implementation
135 | if aLine.hasPrefix(Sb.atImplementation) && !inImplementationTf {
136 | inImplementationTf = true
137 | //暂不处理
138 | continue
139 | }
140 | if aLine.hasPrefix(Sb.atEnd) && inImplementationTf {
141 | inImplementationTf = false
142 | inProtocolTf = false
143 | currentProtocolName = ""
144 | continue
145 | }
146 |
147 | //处理 @protocol
148 | if aLine.hasPrefix(Sb.atProtocol) && !inProtocolTf {
149 | inProtocolTf = true
150 | currentProtocolName = ParsingProtocol.parsingNameFrom(line: aLine)
151 | //检查是否已经存在该protocol
152 | if !protocols.keys.contains(currentProtocolName) {
153 | var newPro = Protocol()
154 | newPro.name = currentProtocolName
155 | protocols[currentProtocolName] = newPro
156 | }
157 | continue
158 | }
159 | if inProtocolTf && currentProtocolName != "" {
160 | //开始处理protocol里的方法
161 | if !aLine.hasPrefix(Sb.atOptional) || !aLine.hasPrefix(Sb.atRequired) {
162 | let pMtd = ParsingMethod.parsing(tokens: ParsingBase.createOCTokens(conent: aLine))
163 | if pMtd.pnameId != "" {
164 | protocols[currentProtocolName]?.methods.append(pMtd)
165 | }
166 | }
167 | }
168 | if aLine.hasPrefix(Sb.atEnd) && inProtocolTf {
169 | inProtocolTf = false
170 | currentProtocolName = ""
171 | continue
172 | }
173 |
174 |
175 | } //遍历lines,行数组
176 |
177 | //测试用
178 | if aFile.name == "SMSubCls.h" {
179 |
180 | }
181 |
182 | //---------根据token切割-----------
183 | //方法解析
184 | var mtdArr = [String]() //方法字符串
185 | var psMtdTf = false //是否在解析方法
186 | var psMtdStep = 0
187 | //方法内部解析
188 | var mtdContentArr = [String]()
189 | var psMtdContentClass = Method() //正在解析的那个方法
190 | var psMtdContentTf = false //是否正在解析那个方法中实现部分内容
191 | var psMtdContentBraceCount = 0 //大括号计数
192 | //获取当前object
193 | var currentObject = Object()
194 |
195 | for tk in tokens {
196 | //h文件 m文件
197 | if aFile.type == FileType.FileH || aFile.type == FileType.FileM {
198 | //设置使用哪个obj,根据implement
199 | if aFile.type == FileType.FileM {
200 | if tk == Sb.atImplementation {
201 | guard let cObject = aFile.objects[tk] else {
202 | continue
203 | }
204 | currentObject = cObject
205 | }
206 |
207 | }
208 | //解析方法内容
209 | if psMtdContentTf {
210 | if tk == Sb.braceL {
211 | mtdContentArr.append(tk)
212 | psMtdContentBraceCount += 1
213 | } else if tk == Sb.braceR {
214 | mtdContentArr.append(tk)
215 | psMtdContentBraceCount -= 1
216 | if psMtdContentBraceCount == 0 {
217 | var reMethod = ParsingMethodContent.parsing(contentArr: mtdContentArr, inMethod: psMtdContentClass)
218 | aFile.methods.append(reMethod)
219 | currentObject.methods.append(reMethod)
220 | reMethod.filePath = aFile.path //将m文件路径赋给方法
221 | methodsDefinedInMFile.append(reMethod)
222 | methodsMFile.append(reMethod.pnameId) //方便快速对比映射用
223 | if reMethod.usedMethod.count > 0 {
224 | for aUsedMethod in reMethod.usedMethod {
225 | //将用过的方法添加到集合中
226 | methodsUsed.append(aUsedMethod.pnameId)
227 | }
228 | }
229 | //结束
230 | mtdContentArr = []
231 | psMtdTf = false
232 | psMtdContentTf = false
233 | }
234 | } else {
235 | //解析方法内容中
236 | //先解析使用的方法
237 | mtdContentArr.append(tk)
238 | }
239 | continue
240 | } //方法内容处理结束
241 |
242 | //方法解析
243 | //如果-和(没有连接起来直接判断不是方法
244 | if psMtdStep == 1 && tk != Sb.rBktL {
245 | psMtdStep = 0
246 | psMtdTf = false
247 | mtdArr = []
248 | }
249 |
250 | if (tk == Sb.minus || tk == Sb.add) && psMtdStep == 0 && !psMtdTf {
251 | psMtdTf = true
252 | psMtdStep = 1;
253 | mtdArr.append(tk)
254 | } else if tk == Sb.rBktL && psMtdStep == 1 && psMtdTf {
255 | psMtdStep = 2;
256 | mtdArr.append(tk)
257 | } else if (tk == Sb.semicolon || tk == Sb.braceL) && psMtdStep == 2 && psMtdTf {
258 | mtdArr.append(tk)
259 | var parsedMethod = ParsingMethod.parsing(tokens: mtdArr)
260 | //开始处理方法内部
261 | if tk == Sb.braceL {
262 | psMtdContentClass = parsedMethod
263 | psMtdContentTf = true
264 | psMtdContentBraceCount += 1
265 | mtdContentArr.append(tk)
266 | } else {
267 | aFile.methods.append(parsedMethod)
268 | parsedMethod.filePath = aFile.path //将h文件的路径赋给方法
269 | methodsDefinedInHFile.append(parsedMethod)
270 | psMtdTf = false
271 | }
272 | //重置
273 | psMtdStep = 0;
274 | mtdArr = []
275 |
276 | } else if psMtdTf {
277 | mtdArr.append(tk)
278 | }
279 |
280 |
281 | } //m和h文件
282 |
283 | } //遍历tokens
284 | //aFile.des()
285 | observer.on(.next(aFile))
286 |
287 |
288 | } //判断地址是否为空
289 |
290 | } //结束所有文件遍历
291 |
292 | //todo:去重
293 | let methodsUsedSet = Set(methodsUsed) //用过方法
294 | let methodsMFileSet = Set(methodsMFile) //m的映射文件
295 | print("H方法:\(methodsDefinedInHFile.count)个")
296 | print("M方法:\(methodsDefinedInMFile.count)个")
297 | print("用过方法(包括系统的):\(methodsUsed.count)个")
298 | //找出h文件中没有用过的方法
299 | var unUsedMethods = [Method]()
300 | for aHMethod in methodsDefinedInHFile {
301 | //todo:第一种无参数的情况暂时先过滤。第二种^这种情况过滤
302 | if aHMethod.params.count == 1 {
303 | if aHMethod.params[0].type == "" {
304 | continue
305 | }
306 | }
307 | if aHMethod.returnTypeBlockTf {
308 | continue
309 | }
310 |
311 | if !methodsUsedSet.contains(aHMethod.pnameId) {
312 | //这里判断的是delegate类型,m里一定没有定义,所以这里过滤了各个delegate
313 | //todo:处理delegate这样的情况
314 | if methodsMFileSet.contains(aHMethod.pnameId) {
315 | //todo:定义一些继承的类,将继承方法加入头文件中的情况
316 | //白名单
317 | // if aHMethod.pnameId == "responseModelWithData:" || aHMethod.pnameId == "initWithTableView:" || aHMethod.pnameId == "setErrMessage:" {
318 | // continue
319 | // }
320 |
321 | unUsedMethods.append(aHMethod)
322 | }
323 | }
324 | }
325 |
326 | observer.on(.next(unUsedMethods))
327 | observer.on(.completed)
328 |
329 | return Disposables.create {
330 | //
331 | }
332 | })
333 |
334 |
335 |
336 | }
337 |
338 | func clean(methods:[Method]) {
339 | //删除
340 | ParsingBase.delete(methods: methods)
341 | }
342 |
343 | }
344 |
--------------------------------------------------------------------------------
/SMCheckProject.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0A12186B1DAFBEED00ADE8DD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12186A1DAFBEED00ADE8DD /* AppDelegate.swift */; };
11 | 0A12186D1DAFBEED00ADE8DD /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12186C1DAFBEED00ADE8DD /* ViewController.swift */; };
12 | 0A12186F1DAFBEED00ADE8DD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A12186E1DAFBEED00ADE8DD /* Assets.xcassets */; };
13 | 0A1218721DAFBEED00ADE8DD /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0A1218701DAFBEED00ADE8DD /* Main.storyboard */; };
14 | 0A13866E1DBF9CA70075D236 /* Sb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A13866D1DBF9CA70075D236 /* Sb.swift */; };
15 | 0A17AC041E66F13700006970 /* CleanUnusedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A17AC031E66F13700006970 /* CleanUnusedImports.swift */; };
16 | 0A19608B1E5ED5C1003D8A45 /* ParsingProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A19608A1E5ED5C1003D8A45 /* ParsingProperty.swift */; };
17 | 0A19608D1E5EF1D2003D8A45 /* ParsingInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A19608C1E5EF1D2003D8A45 /* ParsingInterface.swift */; };
18 | 0A24BAB71DC8D87600848F04 /* DragView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A24BAB61DC8D87600848F04 /* DragView.swift */; };
19 | 0A26A6521E657B3300DC5381 /* ParsingImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A26A6511E657B3300DC5381 /* ParsingImplementation.swift */; };
20 | 0A26A6541E65953300DC5381 /* ParsingProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A26A6531E65953300DC5381 /* ParsingProtocol.swift */; };
21 | 0A2AF81B1DC7564A0050B562 /* CleanUnusedMethods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2AF81A1DC7564A0050B562 /* CleanUnusedMethods.swift */; };
22 | 0A33FE5A1E5D7A6F00E95BCD /* ParsingMacro.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A33FE591E5D7A6F00E95BCD /* ParsingMacro.swift */; };
23 | 0A33FE8B1E5D848C00E95BCD /* ParsingImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A33FE8A1E5D848C00E95BCD /* ParsingImport.swift */; };
24 | 0A8672311DB8CE4300CE1292 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8672301DB8CE4300CE1292 /* File.swift */; };
25 | 0A8672351DB8E82000CE1292 /* ParsingMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8672341DB8E82000CE1292 /* ParsingMethod.swift */; };
26 | 0A8672371DB8FC0600CE1292 /* ParsingBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8672361DB8FC0600CE1292 /* ParsingBase.swift */; };
27 | 3A9262D71DBC860600E74505 /* ParsingMethodContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A9262D61DBC860600E74505 /* ParsingMethodContent.swift */; };
28 | F9127F3D65BC448371541BC2 /* Pods_SMCheckProject.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7698ED9B767599D2C14EC463 /* Pods_SMCheckProject.framework */; };
29 | /* End PBXBuildFile section */
30 |
31 | /* Begin PBXFileReference section */
32 | 0A1218671DAFBEED00ADE8DD /* SMCheckProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SMCheckProject.app; sourceTree = BUILT_PRODUCTS_DIR; };
33 | 0A12186A1DAFBEED00ADE8DD /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
34 | 0A12186C1DAFBEED00ADE8DD /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
35 | 0A12186E1DAFBEED00ADE8DD /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
36 | 0A1218711DAFBEED00ADE8DD /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
37 | 0A1218731DAFBEED00ADE8DD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
38 | 0A13866D1DBF9CA70075D236 /* Sb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sb.swift; sourceTree = ""; };
39 | 0A17AC031E66F13700006970 /* CleanUnusedImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CleanUnusedImports.swift; sourceTree = ""; };
40 | 0A19608A1E5ED5C1003D8A45 /* ParsingProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingProperty.swift; sourceTree = ""; };
41 | 0A19608C1E5EF1D2003D8A45 /* ParsingInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingInterface.swift; sourceTree = ""; };
42 | 0A24BAB61DC8D87600848F04 /* DragView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DragView.swift; sourceTree = ""; };
43 | 0A26A6511E657B3300DC5381 /* ParsingImplementation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingImplementation.swift; sourceTree = ""; };
44 | 0A26A6531E65953300DC5381 /* ParsingProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingProtocol.swift; sourceTree = ""; };
45 | 0A2AF81A1DC7564A0050B562 /* CleanUnusedMethods.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CleanUnusedMethods.swift; sourceTree = ""; };
46 | 0A33FE591E5D7A6F00E95BCD /* ParsingMacro.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingMacro.swift; sourceTree = ""; };
47 | 0A33FE8A1E5D848C00E95BCD /* ParsingImport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingImport.swift; sourceTree = ""; };
48 | 0A8672301DB8CE4300CE1292 /* File.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = ""; };
49 | 0A8672341DB8E82000CE1292 /* ParsingMethod.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingMethod.swift; sourceTree = ""; };
50 | 0A8672361DB8FC0600CE1292 /* ParsingBase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingBase.swift; sourceTree = ""; };
51 | 1D00FD81E243B94178CB346E /* Pods-SMCheckProject.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SMCheckProject.release.xcconfig"; path = "Pods/Target Support Files/Pods-SMCheckProject/Pods-SMCheckProject.release.xcconfig"; sourceTree = ""; };
52 | 3A9262D61DBC860600E74505 /* ParsingMethodContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParsingMethodContent.swift; sourceTree = ""; };
53 | 4E8AF9FDD05066A3E6CEC302 /* Pods-SMCheckProject.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SMCheckProject.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SMCheckProject/Pods-SMCheckProject.debug.xcconfig"; sourceTree = ""; };
54 | 7698ED9B767599D2C14EC463 /* Pods_SMCheckProject.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SMCheckProject.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55 | /* End PBXFileReference section */
56 |
57 | /* Begin PBXFrameworksBuildPhase section */
58 | 0A1218641DAFBEED00ADE8DD /* Frameworks */ = {
59 | isa = PBXFrameworksBuildPhase;
60 | buildActionMask = 2147483647;
61 | files = (
62 | F9127F3D65BC448371541BC2 /* Pods_SMCheckProject.framework in Frameworks */,
63 | );
64 | runOnlyForDeploymentPostprocessing = 0;
65 | };
66 | /* End PBXFrameworksBuildPhase section */
67 |
68 | /* Begin PBXGroup section */
69 | 0A12185E1DAFBEED00ADE8DD = {
70 | isa = PBXGroup;
71 | children = (
72 | 0A1218691DAFBEED00ADE8DD /* SMCheckProject */,
73 | 0A1218681DAFBEED00ADE8DD /* Products */,
74 | BB1E8E91F1090CE8CEDAD30F /* Pods */,
75 | 8A44186870EF7A75906186CC /* Frameworks */,
76 | );
77 | sourceTree = "";
78 | };
79 | 0A1218681DAFBEED00ADE8DD /* Products */ = {
80 | isa = PBXGroup;
81 | children = (
82 | 0A1218671DAFBEED00ADE8DD /* SMCheckProject.app */,
83 | );
84 | name = Products;
85 | sourceTree = "";
86 | };
87 | 0A1218691DAFBEED00ADE8DD /* SMCheckProject */ = {
88 | isa = PBXGroup;
89 | children = (
90 | 0A12186A1DAFBEED00ADE8DD /* AppDelegate.swift */,
91 | 0A12186C1DAFBEED00ADE8DD /* ViewController.swift */,
92 | 0A8672301DB8CE4300CE1292 /* File.swift */,
93 | 0A24BAB61DC8D87600848F04 /* DragView.swift */,
94 | 0A2AF8191DC756190050B562 /* Feature */,
95 | 0A2AF8181DC756050050B562 /* Parsing */,
96 | 0A12186E1DAFBEED00ADE8DD /* Assets.xcassets */,
97 | 0A1218701DAFBEED00ADE8DD /* Main.storyboard */,
98 | 0A1218731DAFBEED00ADE8DD /* Info.plist */,
99 | );
100 | path = SMCheckProject;
101 | sourceTree = "";
102 | };
103 | 0A2AF8181DC756050050B562 /* Parsing */ = {
104 | isa = PBXGroup;
105 | children = (
106 | 0A8672341DB8E82000CE1292 /* ParsingMethod.swift */,
107 | 3A9262D61DBC860600E74505 /* ParsingMethodContent.swift */,
108 | 0A8672361DB8FC0600CE1292 /* ParsingBase.swift */,
109 | 0A13866D1DBF9CA70075D236 /* Sb.swift */,
110 | 0A33FE591E5D7A6F00E95BCD /* ParsingMacro.swift */,
111 | 0A33FE8A1E5D848C00E95BCD /* ParsingImport.swift */,
112 | 0A19608A1E5ED5C1003D8A45 /* ParsingProperty.swift */,
113 | 0A19608C1E5EF1D2003D8A45 /* ParsingInterface.swift */,
114 | 0A26A6511E657B3300DC5381 /* ParsingImplementation.swift */,
115 | 0A26A6531E65953300DC5381 /* ParsingProtocol.swift */,
116 | );
117 | name = Parsing;
118 | sourceTree = "";
119 | };
120 | 0A2AF8191DC756190050B562 /* Feature */ = {
121 | isa = PBXGroup;
122 | children = (
123 | 0A2AF81A1DC7564A0050B562 /* CleanUnusedMethods.swift */,
124 | 0A17AC031E66F13700006970 /* CleanUnusedImports.swift */,
125 | );
126 | name = Feature;
127 | sourceTree = "";
128 | };
129 | 8A44186870EF7A75906186CC /* Frameworks */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 7698ED9B767599D2C14EC463 /* Pods_SMCheckProject.framework */,
133 | );
134 | name = Frameworks;
135 | sourceTree = "";
136 | };
137 | BB1E8E91F1090CE8CEDAD30F /* Pods */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 4E8AF9FDD05066A3E6CEC302 /* Pods-SMCheckProject.debug.xcconfig */,
141 | 1D00FD81E243B94178CB346E /* Pods-SMCheckProject.release.xcconfig */,
142 | );
143 | name = Pods;
144 | sourceTree = "";
145 | };
146 | /* End PBXGroup section */
147 |
148 | /* Begin PBXNativeTarget section */
149 | 0A1218661DAFBEED00ADE8DD /* SMCheckProject */ = {
150 | isa = PBXNativeTarget;
151 | buildConfigurationList = 0A1218761DAFBEED00ADE8DD /* Build configuration list for PBXNativeTarget "SMCheckProject" */;
152 | buildPhases = (
153 | 2727F224E729D913E2982AB6 /* [CP] Check Pods Manifest.lock */,
154 | 0A1218631DAFBEED00ADE8DD /* Sources */,
155 | 0A1218641DAFBEED00ADE8DD /* Frameworks */,
156 | 0A1218651DAFBEED00ADE8DD /* Resources */,
157 | 525F6CAA201DA78F394F83CB /* [CP] Embed Pods Frameworks */,
158 | 3030667CE8B262EEBF2090BE /* [CP] Copy Pods Resources */,
159 | );
160 | buildRules = (
161 | );
162 | dependencies = (
163 | );
164 | name = SMCheckProject;
165 | productName = SMCheckProject;
166 | productReference = 0A1218671DAFBEED00ADE8DD /* SMCheckProject.app */;
167 | productType = "com.apple.product-type.application";
168 | };
169 | /* End PBXNativeTarget section */
170 |
171 | /* Begin PBXProject section */
172 | 0A12185F1DAFBEED00ADE8DD /* Project object */ = {
173 | isa = PBXProject;
174 | attributes = {
175 | LastSwiftUpdateCheck = 0800;
176 | LastUpgradeCheck = 0800;
177 | ORGANIZATIONNAME = Starming;
178 | TargetAttributes = {
179 | 0A1218661DAFBEED00ADE8DD = {
180 | CreatedOnToolsVersion = 8.0;
181 | ProvisioningStyle = Automatic;
182 | };
183 | };
184 | };
185 | buildConfigurationList = 0A1218621DAFBEED00ADE8DD /* Build configuration list for PBXProject "SMCheckProject" */;
186 | compatibilityVersion = "Xcode 3.2";
187 | developmentRegion = English;
188 | hasScannedForEncodings = 0;
189 | knownRegions = (
190 | en,
191 | Base,
192 | );
193 | mainGroup = 0A12185E1DAFBEED00ADE8DD;
194 | productRefGroup = 0A1218681DAFBEED00ADE8DD /* Products */;
195 | projectDirPath = "";
196 | projectRoot = "";
197 | targets = (
198 | 0A1218661DAFBEED00ADE8DD /* SMCheckProject */,
199 | );
200 | };
201 | /* End PBXProject section */
202 |
203 | /* Begin PBXResourcesBuildPhase section */
204 | 0A1218651DAFBEED00ADE8DD /* Resources */ = {
205 | isa = PBXResourcesBuildPhase;
206 | buildActionMask = 2147483647;
207 | files = (
208 | 0A12186F1DAFBEED00ADE8DD /* Assets.xcassets in Resources */,
209 | 0A1218721DAFBEED00ADE8DD /* Main.storyboard in Resources */,
210 | );
211 | runOnlyForDeploymentPostprocessing = 0;
212 | };
213 | /* End PBXResourcesBuildPhase section */
214 |
215 | /* Begin PBXShellScriptBuildPhase section */
216 | 2727F224E729D913E2982AB6 /* [CP] Check Pods Manifest.lock */ = {
217 | isa = PBXShellScriptBuildPhase;
218 | buildActionMask = 2147483647;
219 | files = (
220 | );
221 | inputPaths = (
222 | );
223 | name = "[CP] Check Pods Manifest.lock";
224 | outputPaths = (
225 | );
226 | runOnlyForDeploymentPostprocessing = 0;
227 | shellPath = /bin/sh;
228 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
229 | showEnvVarsInLog = 0;
230 | };
231 | 3030667CE8B262EEBF2090BE /* [CP] Copy Pods Resources */ = {
232 | isa = PBXShellScriptBuildPhase;
233 | buildActionMask = 2147483647;
234 | files = (
235 | );
236 | inputPaths = (
237 | );
238 | name = "[CP] Copy Pods Resources";
239 | outputPaths = (
240 | );
241 | runOnlyForDeploymentPostprocessing = 0;
242 | shellPath = /bin/sh;
243 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SMCheckProject/Pods-SMCheckProject-resources.sh\"\n";
244 | showEnvVarsInLog = 0;
245 | };
246 | 525F6CAA201DA78F394F83CB /* [CP] Embed Pods Frameworks */ = {
247 | isa = PBXShellScriptBuildPhase;
248 | buildActionMask = 2147483647;
249 | files = (
250 | );
251 | inputPaths = (
252 | );
253 | name = "[CP] Embed Pods Frameworks";
254 | outputPaths = (
255 | );
256 | runOnlyForDeploymentPostprocessing = 0;
257 | shellPath = /bin/sh;
258 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SMCheckProject/Pods-SMCheckProject-frameworks.sh\"\n";
259 | showEnvVarsInLog = 0;
260 | };
261 | /* End PBXShellScriptBuildPhase section */
262 |
263 | /* Begin PBXSourcesBuildPhase section */
264 | 0A1218631DAFBEED00ADE8DD /* Sources */ = {
265 | isa = PBXSourcesBuildPhase;
266 | buildActionMask = 2147483647;
267 | files = (
268 | 0A26A6521E657B3300DC5381 /* ParsingImplementation.swift in Sources */,
269 | 0A8672371DB8FC0600CE1292 /* ParsingBase.swift in Sources */,
270 | 0A19608B1E5ED5C1003D8A45 /* ParsingProperty.swift in Sources */,
271 | 0A12186D1DAFBEED00ADE8DD /* ViewController.swift in Sources */,
272 | 0A26A6541E65953300DC5381 /* ParsingProtocol.swift in Sources */,
273 | 0A8672311DB8CE4300CE1292 /* File.swift in Sources */,
274 | 0A13866E1DBF9CA70075D236 /* Sb.swift in Sources */,
275 | 3A9262D71DBC860600E74505 /* ParsingMethodContent.swift in Sources */,
276 | 0A17AC041E66F13700006970 /* CleanUnusedImports.swift in Sources */,
277 | 0A19608D1E5EF1D2003D8A45 /* ParsingInterface.swift in Sources */,
278 | 0A12186B1DAFBEED00ADE8DD /* AppDelegate.swift in Sources */,
279 | 0A2AF81B1DC7564A0050B562 /* CleanUnusedMethods.swift in Sources */,
280 | 0A33FE8B1E5D848C00E95BCD /* ParsingImport.swift in Sources */,
281 | 0A8672351DB8E82000CE1292 /* ParsingMethod.swift in Sources */,
282 | 0A24BAB71DC8D87600848F04 /* DragView.swift in Sources */,
283 | 0A33FE5A1E5D7A6F00E95BCD /* ParsingMacro.swift in Sources */,
284 | );
285 | runOnlyForDeploymentPostprocessing = 0;
286 | };
287 | /* End PBXSourcesBuildPhase section */
288 |
289 | /* Begin PBXVariantGroup section */
290 | 0A1218701DAFBEED00ADE8DD /* Main.storyboard */ = {
291 | isa = PBXVariantGroup;
292 | children = (
293 | 0A1218711DAFBEED00ADE8DD /* Base */,
294 | );
295 | name = Main.storyboard;
296 | sourceTree = "";
297 | };
298 | /* End PBXVariantGroup section */
299 |
300 | /* Begin XCBuildConfiguration section */
301 | 0A1218741DAFBEED00ADE8DD /* Debug */ = {
302 | isa = XCBuildConfiguration;
303 | buildSettings = {
304 | ALWAYS_SEARCH_USER_PATHS = NO;
305 | CLANG_ANALYZER_NONNULL = YES;
306 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
307 | CLANG_CXX_LIBRARY = "libc++";
308 | CLANG_ENABLE_MODULES = YES;
309 | CLANG_ENABLE_OBJC_ARC = YES;
310 | CLANG_WARN_BOOL_CONVERSION = YES;
311 | CLANG_WARN_CONSTANT_CONVERSION = YES;
312 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
313 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
314 | CLANG_WARN_EMPTY_BODY = YES;
315 | CLANG_WARN_ENUM_CONVERSION = YES;
316 | CLANG_WARN_INFINITE_RECURSION = YES;
317 | CLANG_WARN_INT_CONVERSION = YES;
318 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
319 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
320 | CLANG_WARN_UNREACHABLE_CODE = YES;
321 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
322 | CODE_SIGN_IDENTITY = "-";
323 | COPY_PHASE_STRIP = NO;
324 | DEBUG_INFORMATION_FORMAT = dwarf;
325 | ENABLE_STRICT_OBJC_MSGSEND = YES;
326 | ENABLE_TESTABILITY = YES;
327 | GCC_C_LANGUAGE_STANDARD = gnu99;
328 | GCC_DYNAMIC_NO_PIC = NO;
329 | GCC_NO_COMMON_BLOCKS = YES;
330 | GCC_OPTIMIZATION_LEVEL = 0;
331 | GCC_PREPROCESSOR_DEFINITIONS = (
332 | "DEBUG=1",
333 | "$(inherited)",
334 | );
335 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
336 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
337 | GCC_WARN_UNDECLARED_SELECTOR = YES;
338 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
339 | GCC_WARN_UNUSED_FUNCTION = YES;
340 | GCC_WARN_UNUSED_VARIABLE = YES;
341 | MACOSX_DEPLOYMENT_TARGET = 10.12;
342 | MTL_ENABLE_DEBUG_INFO = YES;
343 | ONLY_ACTIVE_ARCH = YES;
344 | SDKROOT = macosx;
345 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
346 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
347 | };
348 | name = Debug;
349 | };
350 | 0A1218751DAFBEED00ADE8DD /* Release */ = {
351 | isa = XCBuildConfiguration;
352 | buildSettings = {
353 | ALWAYS_SEARCH_USER_PATHS = NO;
354 | CLANG_ANALYZER_NONNULL = YES;
355 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
356 | CLANG_CXX_LIBRARY = "libc++";
357 | CLANG_ENABLE_MODULES = YES;
358 | CLANG_ENABLE_OBJC_ARC = YES;
359 | CLANG_WARN_BOOL_CONVERSION = YES;
360 | CLANG_WARN_CONSTANT_CONVERSION = YES;
361 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
362 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
363 | CLANG_WARN_EMPTY_BODY = YES;
364 | CLANG_WARN_ENUM_CONVERSION = YES;
365 | CLANG_WARN_INFINITE_RECURSION = YES;
366 | CLANG_WARN_INT_CONVERSION = YES;
367 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
368 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
369 | CLANG_WARN_UNREACHABLE_CODE = YES;
370 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
371 | CODE_SIGN_IDENTITY = "-";
372 | COPY_PHASE_STRIP = NO;
373 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
374 | ENABLE_NS_ASSERTIONS = NO;
375 | ENABLE_STRICT_OBJC_MSGSEND = YES;
376 | GCC_C_LANGUAGE_STANDARD = gnu99;
377 | GCC_NO_COMMON_BLOCKS = YES;
378 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
379 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
380 | GCC_WARN_UNDECLARED_SELECTOR = YES;
381 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
382 | GCC_WARN_UNUSED_FUNCTION = YES;
383 | GCC_WARN_UNUSED_VARIABLE = YES;
384 | MACOSX_DEPLOYMENT_TARGET = 10.12;
385 | MTL_ENABLE_DEBUG_INFO = NO;
386 | SDKROOT = macosx;
387 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
388 | };
389 | name = Release;
390 | };
391 | 0A1218771DAFBEED00ADE8DD /* Debug */ = {
392 | isa = XCBuildConfiguration;
393 | baseConfigurationReference = 4E8AF9FDD05066A3E6CEC302 /* Pods-SMCheckProject.debug.xcconfig */;
394 | buildSettings = {
395 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
396 | COMBINE_HIDPI_IMAGES = YES;
397 | INFOPLIST_FILE = SMCheckProject/Info.plist;
398 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
399 | PRODUCT_BUNDLE_IDENTIFIER = com.starming.SMCheckProject;
400 | PRODUCT_NAME = "$(TARGET_NAME)";
401 | SWIFT_VERSION = 3.0;
402 | };
403 | name = Debug;
404 | };
405 | 0A1218781DAFBEED00ADE8DD /* Release */ = {
406 | isa = XCBuildConfiguration;
407 | baseConfigurationReference = 1D00FD81E243B94178CB346E /* Pods-SMCheckProject.release.xcconfig */;
408 | buildSettings = {
409 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
410 | COMBINE_HIDPI_IMAGES = YES;
411 | INFOPLIST_FILE = SMCheckProject/Info.plist;
412 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
413 | PRODUCT_BUNDLE_IDENTIFIER = com.starming.SMCheckProject;
414 | PRODUCT_NAME = "$(TARGET_NAME)";
415 | SWIFT_VERSION = 3.0;
416 | };
417 | name = Release;
418 | };
419 | /* End XCBuildConfiguration section */
420 |
421 | /* Begin XCConfigurationList section */
422 | 0A1218621DAFBEED00ADE8DD /* Build configuration list for PBXProject "SMCheckProject" */ = {
423 | isa = XCConfigurationList;
424 | buildConfigurations = (
425 | 0A1218741DAFBEED00ADE8DD /* Debug */,
426 | 0A1218751DAFBEED00ADE8DD /* Release */,
427 | );
428 | defaultConfigurationIsVisible = 0;
429 | defaultConfigurationName = Release;
430 | };
431 | 0A1218761DAFBEED00ADE8DD /* Build configuration list for PBXNativeTarget "SMCheckProject" */ = {
432 | isa = XCConfigurationList;
433 | buildConfigurations = (
434 | 0A1218771DAFBEED00ADE8DD /* Debug */,
435 | 0A1218781DAFBEED00ADE8DD /* Release */,
436 | );
437 | defaultConfigurationIsVisible = 0;
438 | defaultConfigurationName = Release;
439 | };
440 | /* End XCConfigurationList section */
441 | };
442 | rootObject = 0A12185F1DAFBEED00ADE8DD /* Project object */;
443 | }
444 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 使用Swift3开发了个MacOS的程序可以检测出objc项目中无用方法,然后一键全部清理
2 | 当项目越来越大,引入第三方库越来越多,上架的 APP 体积也会越来越大,对于用户来说体验必定是不好的。在清理资源,编译选项优化,清理无用类等完成后,能够做而且效果会比较明显的就只有清理无用函数了。
3 |
4 | 一种方案是我们滴滴的王康基于clang插件这样一个源码级别的分析工具来分析代码间的调用关系达到分析出无用代码的目的,文章在这里: [基于clang插件的一种iOS包大小瘦身方案](http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=2651112856&idx=1&sn=b2c74c62a10b4c9a4e7538d1ad7eb739) 文章里对objc方法的定义,调用,实现的全面说明达到了极致,非常值得一看。
5 |
6 | 另一种方案是根据 *Linkmap* 文件取到objc的所有类方法和实例方法。再用工具比如 otool 命令逆向出可执行文件里引用到的方法名然后通过求差集得到无用函数,由于API的回调也会被认为是无用函数,所以这个方案还需要将这些回调函数加到白名单里过滤。具体说明,可以看看微信团队的这篇文章: [iOS微信安装包瘦身](http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=207986417&idx=1&sn=77ea7d8e4f8ab7b59111e78c86ccfe66&3rd=MzA3MDU4NTYzMw==&scene=6#rd)
7 |
8 | 还有一种使用了 * [machoview](https://sourceforge.net/projects/machoview/) * 从 *Mach-O* 里获取信息进行无用方法和文件的处理。阿里有篇文章对 Mach-O 的处理做了详细的说明: [减小ipa体积之删除frameWork中无用mach-O文件](http://jaq.alibaba.com/community/art/show?articleid=229)
9 |
10 | 这几个现有方案有些比较麻烦的地方,因为检索出的无用方法没法确定能够直接删除,还需要挨个检索人工判断是否可以删除,这样每次要清理时都需要这样人工排查一遍是非常耗时耗力的。
11 |
12 | 这样就只有模拟编译过程对代码进行深入分析才能够找出确定能够删除的方法。具体效果可以先试试看,程序代码在: 选择工程目录后程序就开始检索无用方法然后将其注释掉。
13 |
14 | ## 设置结构体 😚
15 | 首先确定结构,类似先把 *OC* 文件根据语法画出整体结构。先看看 *OC Runtime* 里是如何设计的结构体。
16 | ```c
17 | struct objc_object {
18 | Class isa OBJC_ISA_AVAILABILITY;
19 | };
20 |
21 | /*类*/
22 | struct objc_class {
23 | Class isa OBJC_ISA_AVAILABILITY;
24 | #if !__OBJC2__
25 | Class super_class;
26 | const char *name;
27 | long version;
28 | long info;
29 | long instance_size;
30 | struct objc_ivar_list *ivars;
31 | struct objc_method_list **methodLists;
32 | struct objc_cache *cache;
33 | struct objc_protocol_list *protocols;
34 | #endif
35 | };
36 |
37 | /*成员变量列表*/
38 | struct objc_ivar_list {
39 | int ivar_count
40 | #ifdef __LP64__
41 | int space
42 | #endif
43 | /* variable length structure */
44 | struct objc_ivar ivar_list[1]
45 | }
46 |
47 | /*成员变量结构体*/
48 | struct objc_ivar {
49 | char *ivar_name
50 | char *ivar_type
51 | int ivar_offset
52 | #ifdef __LP64__
53 | int space
54 | #endif
55 | }
56 |
57 | /*方法列表*/
58 | struct objc_method_list {
59 | struct objc_method_list *obsolete;
60 | int method_count;
61 |
62 | #ifdef __LP64__
63 | int space;
64 | #endif
65 | /* variable length structure */
66 | struct objc_method method_list[1];
67 | };
68 |
69 | /*方法结构体*/
70 | struct objc_method {
71 | SEL method_name;
72 | char *method_types; /* a string representing argument/return types */
73 | IMP method_imp;
74 | };
75 | ```
76 | 一个 class 只有少量函数会被调用,为了减少较大的遍历所以创建一个 *objc_cache* ,在找到一个方法后将 *method_name* 作为 key,将 *method_imp* 做值,再次发起时就可以直接在 cache 里找。
77 |
78 | 使用 swift 创建类似的结构体,做些修改
79 | ```swift
80 | //文件
81 | class File: NSObject {
82 | //文件
83 | public var type = FileType.FileH
84 | public var name = ""
85 | public var content = ""
86 | public var methods = [Method]() //所有方法
87 | public var imports = [Import]() //引入类
88 | }
89 |
90 | //引入
91 | struct Import {
92 | public var fileName = ""
93 | }
94 |
95 | //对象
96 | class Object {
97 | public var name = ""
98 | public var superObject = ""
99 | public var properties = [Property]()
100 | public var methods = [Method]()
101 | }
102 |
103 | //成员变量
104 | struct Property {
105 | public var name = ""
106 | public var type = ""
107 | }
108 |
109 | struct Method {
110 | public var classMethodTf = false //+ or -
111 | public var returnType = ""
112 | public var returnTypePointTf = false
113 | public var returnTypeBlockTf = false
114 | public var params = [MethodParam]()
115 | public var usedMethod = [Method]()
116 | public var filePath = "" //定义方法的文件路径,方便修改文件使用
117 | public var pnameId = "" //唯一标识,便于快速比较
118 | }
119 |
120 | class MethodParam: NSObject {
121 | public var name = ""
122 | public var type = ""
123 | public var typePointTf = false
124 | public var iName = ""
125 | }
126 |
127 | class Type: NSObject {
128 | //todo:更多类型
129 | public var name = ""
130 | public var type = 0 //0是值类型 1是指针
131 | }
132 | ```swift
133 |
134 | ## 开始语法解析 😈
135 | 首先遍历目录下所有的文件。
136 | ```swift
137 | let fileFolderPath = self.selectFolder()
138 | let fileFolderStringPath = fileFolderPath.replacingOccurrences(of: "file://", with: "")
139 | let fileManager = FileManager.default;
140 | //深度遍历
141 | let enumeratorAtPath = fileManager.enumerator(atPath: fileFolderStringPath)
142 | //过滤文件后缀
143 | let filterPath = NSArray(array: (enumeratorAtPath?.allObjects)!).pathsMatchingExtensions(["h","m"])
144 | ```
145 |
146 | 然后将注释排除在分析之外,这样做能够有效避免无用的解析。
147 |
148 | 分析是否需要按照行来切割,在 *@interface* , *@end* 和 *@ implementation* , *@end* 里面不需要换行,按照;符号,外部需要按行来。所以两种切割都需要。
149 |
150 | 先定义语法标识符
151 | ```swift
152 | class Sb: NSObject {
153 | public static let add = "+"
154 | public static let minus = "-"
155 | public static let rBktL = "("
156 | public static let rBktR = ")"
157 | public static let asterisk = "*"
158 | public static let colon = ":"
159 | public static let semicolon = ";"
160 | public static let divide = "/"
161 | public static let agBktL = "<"
162 | public static let agBktR = ">"
163 | public static let quotM = "\""
164 | public static let pSign = "#"
165 | public static let braceL = "{"
166 | public static let braceR = "}"
167 | public static let bktL = "["
168 | public static let bktR = "]"
169 | public static let qM = "?"
170 | public static let upArrow = "^"
171 |
172 | public static let inteface = "@interface"
173 | public static let implementation = "@implementation"
174 | public static let end = "@end"
175 | public static let selector = "@selector"
176 |
177 | public static let space = " "
178 | public static let newLine = "\n"
179 | }
180 | ```
181 |
182 | 接下来就要开始根据标记符号来进行切割分组了,使用 *Scanner* ,具体方式如下
183 | ```swift
184 | //根据代码文件解析出一个根据标记符切分的数组
185 | class func createOCTokens(conent:String) -> [String] {
186 | var str = conent
187 |
188 | str = self.dislodgeAnnotaion(content: str)
189 |
190 | //开始扫描切割
191 | let scanner = Scanner(string: str)
192 | var tokens = [String]()
193 | //Todo:待处理符号,.
194 | let operaters = [Sb.add,Sb.minus,Sb.rBktL,Sb.rBktR,Sb.asterisk,Sb.colon,Sb.semicolon,Sb.divide,Sb.agBktL,Sb.agBktR,Sb.quotM,Sb.pSign,Sb.braceL,Sb.braceR,Sb.bktL,Sb.bktR,Sb.qM]
195 | var operatersString = ""
196 | for op in operaters {
197 | operatersString = operatersString.appending(op)
198 | }
199 |
200 | var set = CharacterSet()
201 | set.insert(charactersIn: operatersString)
202 | set.formUnion(CharacterSet.whitespacesAndNewlines)
203 |
204 | while !scanner.isAtEnd {
205 | for operater in operaters {
206 | if (scanner.scanString(operater, into: nil)) {
207 | tokens.append(operater)
208 | }
209 | }
210 |
211 | var result:NSString?
212 | result = nil;
213 | if scanner.scanUpToCharacters(from: set, into: &result) {
214 | tokens.append(result as! String)
215 | }
216 | }
217 | tokens = tokens.filter {
218 | $0 != Sb.space
219 | }
220 | return tokens;
221 | }
222 | ```
223 |
224 | 行解析的方法
225 | ```swift
226 | //根据代码文件解析出一个根据行切分的数组
227 | class func createOCLines(content:String) -> [String] {
228 | var str = content
229 | str = self.dislodgeAnnotaion(content: str)
230 | let strArr = str.components(separatedBy: CharacterSet.newlines)
231 | return strArr
232 | }
233 | ```
234 |
235 | ## 根据结构将定义的方法取出 🤖
236 | ```objective-c
237 | - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path cacheTime:(NSInteger)cacheTime subDirectory:(NSString*)subDirectory;
238 | ```
239 | 这里按照语法规则顺序取出即可,将方法名,返回类型,参数名,参数类型记录。这里需要注意 *Block* 类型的参数
240 | ```objective-c
241 | - (STMPartMaker *(^)(STMPartColorType))colorTypeIs;
242 | ```
243 | 这种类型中还带有括号的语法的解析,这里用到的方法是对括号进行计数,左括号加一右括号减一的方式取得完整方法。
244 |
245 | 获得这些数据后就可以开始检索定义的方法了。我写了一个类专门用来获得所有定义的方法
246 | ```swift
247 | class func parsingWithArray(arr:Array) -> Method {
248 | var mtd = Method()
249 | var returnTypeTf = false //是否取得返回类型
250 | var parsingTf = false //解析中
251 | var bracketCount = 0 //括弧计数
252 | var step = 0 //1获取参数名,2获取参数类型,3获取iName
253 | var types = [String]()
254 | var methodParam = MethodParam()
255 | //print("\(arr)")
256 | for var tk in arr {
257 | tk = tk.replacingOccurrences(of: Sb.newLine, with: "")
258 | if (tk == Sb.semicolon || tk == Sb.braceL) && step != 1 {
259 | var shouldAdd = false
260 |
261 | if mtd.params.count > 1 {
262 | //处理这种- (void)initWithC:(type)m m2:(type2)i, ... NS_REQUIRES_NIL_TERMINATION;入参为多参数情况
263 | if methodParam.type.characters.count > 0 {
264 | shouldAdd = true
265 | }
266 | } else {
267 | shouldAdd = true
268 | }
269 | if shouldAdd {
270 | mtd.params.append(methodParam)
271 | mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")
272 | }
273 |
274 | } else if tk == Sb.rBktL {
275 | bracketCount += 1
276 | parsingTf = true
277 | } else if tk == Sb.rBktR {
278 | bracketCount -= 1
279 | if bracketCount == 0 {
280 | var typeString = ""
281 | for typeTk in types {
282 | typeString = typeString.appending(typeTk)
283 | }
284 | if !returnTypeTf {
285 | //完成获取返回
286 | mtd.returnType = typeString
287 | step = 1
288 | returnTypeTf = true
289 | } else {
290 | if step == 2 {
291 | methodParam.type = typeString
292 | step = 3
293 | }
294 |
295 | }
296 | //括弧结束后的重置工作
297 | parsingTf = false
298 | types = []
299 | }
300 | } else if parsingTf {
301 | types.append(tk)
302 | //todo:返回block类型会使用.设置值的方式,目前获取用过方法方式没有.这种的解析,暂时作为
303 | if tk == Sb.upArrow {
304 | mtd.returnTypeBlockTf = true
305 | }
306 | } else if tk == Sb.colon {
307 | step = 2
308 | } else if step == 1 {
309 | if tk == "initWithCoordinate" {
310 | //
311 | }
312 | methodParam.name = tk
313 | step = 0
314 | } else if step == 3 {
315 | methodParam.iName = tk
316 | step = 1
317 | mtd.params.append(methodParam)
318 | mtd.pnameId = mtd.pnameId.appending("\(methodParam.name):")
319 | methodParam = MethodParam()
320 | } else if tk != Sb.minus && tk != Sb.add {
321 | methodParam.name = tk
322 | }
323 |
324 | }//遍历
325 |
326 | return mtd
327 | }
328 | ```
329 | 这个方法大概的思路就是根据标记符设置不同的状态,然后将获取的信息放入定义的结构中。
330 |
331 | ## 使用过的方法的解析 😱
332 | 进行使用过的方法解析前需要处理的事情
333 | * @“…” 里面的数据,因为这里面是允许我们定义的标识符出现的。
334 | * 递归出文件中 import 所有的类,根据对类的使用可以清除无用的 import
335 | * 继承链的获取。
336 | * 解析获取实例化了的成员变量列表。在解析时需要依赖列表里的成员变量名和变量的类进行方法的完整获取。
337 |
338 | 简单的方法
339 | ```objective-c
340 | [view update:status animation:YES];
341 | ```
342 | 从左到右按照 : 符号获取
343 |
344 | 方法嵌套调用,下面这种情况如何解析出
345 | ```objective-c
346 | @weakify(self);
347 | [[[[[[SMNetManager shareInstance] fetchAllFeedWithModelArray:self.feeds] map:^id(NSNumber *value) {
348 | @strongify(self);
349 | NSUInteger index = [value integerValue];
350 | self.feeds[index] = [SMNetManager shareInstance].feeds[index];
351 | return self.feeds[index];
352 | }] doCompleted:^{
353 | //抓完所有的feeds
354 | @strongify(self);
355 | NSLog(@"fetch complete");
356 | //完成置为默认状态
357 | self.tbHeaderLabel.text = @"";
358 | self.tableView.tableHeaderView = [[UIView alloc] init];
359 | self.fetchingCount = 0;
360 | [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
361 | //下拉刷新关闭
362 | [self.tableView.mj_header endRefreshing];
363 | //更新列表
364 | [self.tableView reloadData];
365 | //检查是否需要增加源
366 | if ([SMFeedStore defaultFeeds].count > self.feeds.count) {
367 | self.feeds = [SMFeedStore defaultFeeds];
368 | [self fetchAllFeeds];
369 | }
370 | }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(SMFeedModel *feedModel) {
371 | //抓完一个
372 | @strongify(self);
373 | self.tableView.tableHeaderView = self.tbHeaderView;
374 | //显示抓取状态
375 | self.fetchingCount += 1;
376 | self.tbHeaderLabel.text = [NSString stringWithFormat:@"正在获取%@...(%lu/%lu)",feedModel.title,(unsigned long)self.fetchingCount,(unsigned long)self.feeds.count];
377 | [self.tableView reloadData];
378 | }];
379 | ```
380 |
381 | 一开始会想到使用递归,以前我做 *STMAssembleView* 时就是使用的递归,这样时间复杂度就会是 O(nlogn) ,这次我换了个思路,将复杂度降低到了 n ,思路大概是 创建一个字典,键值就是深度,从左到右深度的增加根据 *[* 符号,减少根据 *]* 符号,值会在 *[* 时创建一个 *Method* 结构体,根据]来完成结构体,将其添加到 *methods* 数组中 。
382 |
383 | 具体实现如下
384 | ```swift
385 | class func parsing(contentArr:Array, inMethod:Method) -> Method {
386 | var mtdIn = inMethod
387 | //处理用过的方法
388 | //todo:还要过滤@""这种情况
389 | var psBrcStep = 0
390 | var uMtdDic = [Int:Method]()
391 | var preTk = ""
392 | //处理?:这种条件判断简写方式
393 | var psCdtTf = false
394 | var psCdtStep = 0
395 | //判断selector
396 | var psSelectorTf = false
397 | var preSelectorTk = ""
398 | var selectorMtd = Method()
399 | var selectorMtdPar = MethodParam()
400 |
401 | uMtdDic[psBrcStep] = Method() //初始时就实例化一个method,避免在define里定义只定义]符号
402 |
403 | for var tk in contentArr {
404 | //selector处理
405 | if psSelectorTf {
406 | if tk == Sb.colon {
407 | selectorMtdPar.name = preSelectorTk
408 | selectorMtd.params.append(selectorMtdPar)
409 | selectorMtd.pnameId += "\(selectorMtdPar.name):"
410 | } else if tk == Sb.rBktR {
411 | mtdIn.usedMethod.append(selectorMtd)
412 | psSelectorTf = false
413 | selectorMtd = Method()
414 | selectorMtdPar = MethodParam()
415 | } else {
416 | preSelectorTk = tk
417 | }
418 | continue
419 | }
420 | if tk == Sb.selector {
421 | psSelectorTf = true
422 | selectorMtd = Method()
423 | selectorMtdPar = MethodParam()
424 | continue
425 | }
426 | //通常处理
427 | if tk == Sb.bktL {
428 | if psCdtTf {
429 | psCdtStep += 1
430 | }
431 | psBrcStep += 1
432 | uMtdDic[psBrcStep] = Method()
433 | } else if tk == Sb.bktR {
434 | if psCdtTf {
435 | psCdtStep -= 1
436 | }
437 | if (uMtdDic[psBrcStep]?.params.count)! > 0 {
438 | mtdIn.usedMethod.append(uMtdDic[psBrcStep]!)
439 | }
440 | psBrcStep -= 1
441 | //[]不配对的容错处理
442 | if psBrcStep < 0 {
443 | psBrcStep = 0
444 | }
445 |
446 | } else if tk == Sb.colon {
447 | //条件简写情况处理
448 | if psCdtTf && psCdtStep == 0 {
449 | psCdtTf = false
450 | continue
451 | }
452 | //dictionary情况处理@"key":@"value"
453 | if preTk == Sb.quotM || preTk == "respondsToSelector" {
454 | continue
455 | }
456 | let prm = MethodParam()
457 | prm.name = preTk
458 | if prm.name != "" {
459 | uMtdDic[psBrcStep]?.params.append(prm)
460 | uMtdDic[psBrcStep]?.pnameId = (uMtdDic[psBrcStep]?.pnameId.appending("\(prm.name):"))!
461 | }
462 | } else if tk == Sb.qM {
463 | psCdtTf = true
464 | } else {
465 | tk = tk.replacingOccurrences(of: Sb.newLine, with: "")
466 | preTk = tk
467 | }
468 | }
469 |
470 | return mtdIn
471 | }
472 | ```
473 | 在设置 *Method* 结构体时将参数名拼接起来成为 *Method* 的识别符用于后面处理时的快速比对。
474 |
475 | 解析使用过的方法时有几个问题需要注意下
476 | 1.在方法内使用的方法,会有 *respondsToSelector* , *@selector* 还有条件简写语法的情况需要单独处理下。
477 | 2.在 *#define* 里定义使用了方法
478 | ```objective-c
479 | #define CLASS_VALUE(x) [NSValue valueWithNonretainedObject:(x)]
480 | ```
481 |
482 | ## 找出无用方法 😄
483 | 获取到所有使用方法后进行去重,和定义方法进行匹对求出差集,即全部未使用的方法。
484 |
485 | ## 去除无用方法 😎
486 | 比对后获得无用方法后就要开始注释掉他们了。遍历未使用的方法,根据先前 *Method* 结构体中定义了方法所在文件路径,根据文件集结构和File的结构体,可以避免 IO ,直接获取方法对应的文件内容和路径。
487 | 对文件内容进行行切割,逐行检测方法名和参数,匹对时开始对行加上注释, h 文件已;符号为结束, m 文件会对大括号进行计数,逐行注释。实现的方法具体如下:
488 | ```swift
489 | //删除指定的一组方法
490 | class func delete(methods:[Method]) {
491 | print("无用方法")
492 | for aMethod in methods {
493 | print("\(File.desDefineMethodParams(paramArr: aMethod.params))")
494 |
495 | //开始删除
496 | //continue
497 | var hContent = ""
498 | var mContent = ""
499 | var mFilePath = aMethod.filePath
500 | if aMethod.filePath.hasSuffix(".h") {
501 | hContent = try! String(contentsOf: URL(string:aMethod.filePath)!, encoding: String.Encoding.utf8)
502 | //todo:因为先处理了h文件的情况
503 | mFilePath = aMethod.filePath.trimmingCharacters(in: CharacterSet(charactersIn: "h")) //去除头尾字符集
504 | mFilePath = mFilePath.appending("m")
505 | }
506 | if mFilePath.hasSuffix(".m") {
507 | do {
508 | mContent = try String(contentsOf: URL(string:mFilePath)!, encoding: String.Encoding.utf8)
509 | } catch {
510 | mContent = ""
511 | }
512 |
513 | }
514 |
515 | let hContentArr = hContent.components(separatedBy: CharacterSet.newlines)
516 | let mContentArr = mContent.components(separatedBy: CharacterSet.newlines)
517 | //print(mContentArr)
518 | //----------------h文件------------------
519 | var psHMtdTf = false
520 | var hMtds = [String]()
521 | var hMtdStr = ""
522 | var hMtdAnnoStr = ""
523 | var hContentCleaned = ""
524 | for hOneLine in hContentArr {
525 | var line = hOneLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
526 |
527 | if line.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {
528 | psHMtdTf = true
529 | hMtds += self.createOCTokens(conent: line)
530 | hMtdStr = hMtdStr.appending(hOneLine + Sb.newLine)
531 | hMtdAnnoStr += "//-----由SMCheckProject工具删除-----\n//"
532 | hMtdAnnoStr += hOneLine + Sb.newLine
533 | line = self.dislodgeAnnotaionInOneLine(content: line)
534 | line = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
535 | } else if psHMtdTf {
536 | hMtds += self.createOCTokens(conent: line)
537 | hMtdStr = hMtdStr.appending(hOneLine + Sb.newLine)
538 | hMtdAnnoStr += "//" + hOneLine + Sb.newLine
539 | line = self.dislodgeAnnotaionInOneLine(content: line)
540 | line = line.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
541 | } else {
542 | hContentCleaned += hOneLine + Sb.newLine
543 | }
544 |
545 | if line.hasSuffix(Sb.semicolon) && psHMtdTf{
546 | psHMtdTf = false
547 |
548 | let methodPnameId = ParsingMethod.parsingWithArray(arr: hMtds).pnameId
549 | if aMethod.pnameId == methodPnameId {
550 | hContentCleaned += hMtdAnnoStr
551 |
552 | } else {
553 | hContentCleaned += hMtdStr
554 | }
555 | hMtdAnnoStr = ""
556 | hMtdStr = ""
557 | hMtds = []
558 | }
559 |
560 |
561 | }
562 | //删除无用函数
563 | try! hContentCleaned.write(to: URL(string:aMethod.filePath)!, atomically: false, encoding: String.Encoding.utf8)
564 |
565 | //----------------m文件----------------
566 | var mDeletingTf = false
567 | var mBraceCount = 0
568 | var mContentCleaned = ""
569 | var mMtdStr = ""
570 | var mMtdAnnoStr = ""
571 | var mMtds = [String]()
572 | var psMMtdTf = false
573 | for mOneLine in mContentArr {
574 | let line = mOneLine.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
575 |
576 | if mDeletingTf {
577 | let lTokens = self.createOCTokens(conent: line)
578 | mMtdAnnoStr += "//" + mOneLine + Sb.newLine
579 | for tk in lTokens {
580 | if tk == Sb.braceL {
581 | mBraceCount += 1
582 | }
583 | if tk == Sb.braceR {
584 | mBraceCount -= 1
585 | if mBraceCount == 0 {
586 | mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)
587 | mMtdAnnoStr = ""
588 | mDeletingTf = false
589 | }
590 | }
591 | }
592 |
593 | continue
594 | }
595 |
596 |
597 | if line.hasPrefix(Sb.minus) || line.hasPrefix(Sb.add) {
598 | psMMtdTf = true
599 | mMtds += self.createOCTokens(conent: line)
600 | mMtdStr = mMtdStr.appending(mOneLine + Sb.newLine)
601 | mMtdAnnoStr += "//-----由SMCheckProject工具删除-----\n//" + mOneLine + Sb.newLine
602 | } else if psMMtdTf {
603 | mMtdStr = mMtdStr.appending(mOneLine + Sb.newLine)
604 | mMtdAnnoStr += "//" + mOneLine + Sb.newLine
605 | mMtds += self.createOCTokens(conent: line)
606 | } else {
607 | mContentCleaned = mContentCleaned.appending(mOneLine + Sb.newLine)
608 | }
609 |
610 | if line.hasSuffix(Sb.braceL) && psMMtdTf {
611 | psMMtdTf = false
612 | let methodPnameId = ParsingMethod.parsingWithArray(arr: mMtds).pnameId
613 | if aMethod.pnameId == methodPnameId {
614 | mDeletingTf = true
615 | mBraceCount += 1
616 | mContentCleaned = mContentCleaned.appending(mMtdAnnoStr)
617 | } else {
618 | mContentCleaned = mContentCleaned.appending(mMtdStr)
619 | }
620 | mMtdStr = ""
621 | mMtdAnnoStr = ""
622 | mMtds = []
623 | }
624 |
625 | } //m文件
626 |
627 | //删除无用函数
628 | if mContent.characters.count > 0 {
629 | try! mContentCleaned.write(to: URL(string:mFilePath)!, atomically: false, encoding: String.Encoding.utf8)
630 | }
631 |
632 | }
633 | }
634 | ```
635 |
636 | 完整代码在: 这里。
637 |
638 | ## 后记 🦁
639 | 有了这样的结构数据就可以模拟更多人工检测的方式来检测项目。
640 |
641 | 通过获取的方法结合获取类里面定义的局部变量和全局变量,在解析过程中模拟引用的计数来分析循环引用等等类似这样的检测。
642 | 通过获取的类的完整结构还能够将其转成JavaScriptCore能解析的js语法文件等等。
643 |
644 | ## 对于APP瘦身的一些想法 👽
645 | 瘦身应该从平时开发时就需要注意。除了功能和组件上的复用外还需要对堆栈逻辑进行封装以达到代码压缩的效果。
646 |
647 | 比如使用ReactiveCocoa和RxSwift这样的函数响应式编程库提供的方法和编程模式进行
648 |
649 | 对于UI的视图逻辑可以使用一套统一逻辑压缩代码使用DSL来简化写法等。
650 |
651 |
652 |
653 |
--------------------------------------------------------------------------------
/SMCheckProject/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
--------------------------------------------------------------------------------