├── 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 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | Default 511 | 512 | 513 | 514 | 515 | 516 | 517 | Left to Right 518 | 519 | 520 | 521 | 522 | 523 | 524 | Right to Left 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | Default 536 | 537 | 538 | 539 | 540 | 541 | 542 | Left to Right 543 | 544 | 545 | 546 | 547 | 548 | 549 | Right to Left 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 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 | 789 | 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 | 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 | --------------------------------------------------------------------------------