├── .DS_Store ├── .gitignore ├── GenerateSpamCode ├── KLGenerateSpamCode.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── KLGenerateSpamCode └── main.m ├── LICENSE ├── README.md └── images └── p2.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glegoo/unity-objc-spam-generater/cdf9202862c02f59978f8b5be45a95041017d1d6/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /GenerateSpamCode: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glegoo/unity-objc-spam-generater/cdf9202862c02f59978f8b5be45a95041017d1d6/GenerateSpamCode -------------------------------------------------------------------------------- /KLGenerateSpamCode.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 24DE523A1F3437910050B37D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 24DE52391F3437910050B37D /* main.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 24934DDD1F0CD64800958090 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = /usr/share/man/man1/; 18 | dstSubfolderSpec = 0; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 1; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 24934DDF1F0CD64800958090 /* KLGenerateSpamCode */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = KLGenerateSpamCode; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 24DE52391F3437910050B37D /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 28 | /* End PBXFileReference section */ 29 | 30 | /* Begin PBXFrameworksBuildPhase section */ 31 | 24934DDC1F0CD64800958090 /* Frameworks */ = { 32 | isa = PBXFrameworksBuildPhase; 33 | buildActionMask = 2147483647; 34 | files = ( 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 24934DD61F0CD64800958090 = { 42 | isa = PBXGroup; 43 | children = ( 44 | 24DE52381F3437910050B37D /* KLGenerateSpamCode */, 45 | 24934DE01F0CD64800958090 /* Products */, 46 | ); 47 | sourceTree = ""; 48 | }; 49 | 24934DE01F0CD64800958090 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 24934DDF1F0CD64800958090 /* KLGenerateSpamCode */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 24DE52381F3437910050B37D /* KLGenerateSpamCode */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | 24DE52391F3437910050B37D /* main.m */, 61 | ); 62 | path = KLGenerateSpamCode; 63 | sourceTree = ""; 64 | }; 65 | /* End PBXGroup section */ 66 | 67 | /* Begin PBXNativeTarget section */ 68 | 24934DDE1F0CD64800958090 /* KLGenerateSpamCode */ = { 69 | isa = PBXNativeTarget; 70 | buildConfigurationList = 24934DE61F0CD64800958090 /* Build configuration list for PBXNativeTarget "KLGenerateSpamCode" */; 71 | buildPhases = ( 72 | 24934DDB1F0CD64800958090 /* Sources */, 73 | 24934DDC1F0CD64800958090 /* Frameworks */, 74 | 24934DDD1F0CD64800958090 /* CopyFiles */, 75 | ); 76 | buildRules = ( 77 | ); 78 | dependencies = ( 79 | ); 80 | name = KLGenerateSpamCode; 81 | productName = generateSpamCode; 82 | productReference = 24934DDF1F0CD64800958090 /* KLGenerateSpamCode */; 83 | productType = "com.apple.product-type.tool"; 84 | }; 85 | /* End PBXNativeTarget section */ 86 | 87 | /* Begin PBXProject section */ 88 | 24934DD71F0CD64800958090 /* Project object */ = { 89 | isa = PBXProject; 90 | attributes = { 91 | LastUpgradeCheck = 0900; 92 | ORGANIZATIONNAME = GAEA; 93 | TargetAttributes = { 94 | 24934DDE1F0CD64800958090 = { 95 | CreatedOnToolsVersion = 8.2.1; 96 | ProvisioningStyle = Automatic; 97 | }; 98 | }; 99 | }; 100 | buildConfigurationList = 24934DDA1F0CD64800958090 /* Build configuration list for PBXProject "KLGenerateSpamCode" */; 101 | compatibilityVersion = "Xcode 3.2"; 102 | developmentRegion = English; 103 | hasScannedForEncodings = 0; 104 | knownRegions = ( 105 | en, 106 | ); 107 | mainGroup = 24934DD61F0CD64800958090; 108 | productRefGroup = 24934DE01F0CD64800958090 /* Products */; 109 | projectDirPath = ""; 110 | projectRoot = ""; 111 | targets = ( 112 | 24934DDE1F0CD64800958090 /* KLGenerateSpamCode */, 113 | ); 114 | }; 115 | /* End PBXProject section */ 116 | 117 | /* Begin PBXSourcesBuildPhase section */ 118 | 24934DDB1F0CD64800958090 /* Sources */ = { 119 | isa = PBXSourcesBuildPhase; 120 | buildActionMask = 2147483647; 121 | files = ( 122 | 24DE523A1F3437910050B37D /* main.m in Sources */, 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | /* End PBXSourcesBuildPhase section */ 127 | 128 | /* Begin XCBuildConfiguration section */ 129 | 24934DE41F0CD64800958090 /* Debug */ = { 130 | isa = XCBuildConfiguration; 131 | buildSettings = { 132 | ALWAYS_SEARCH_USER_PATHS = NO; 133 | CLANG_ANALYZER_NONNULL = YES; 134 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 135 | CLANG_CXX_LIBRARY = "libc++"; 136 | CLANG_ENABLE_MODULES = YES; 137 | CLANG_ENABLE_OBJC_ARC = YES; 138 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 139 | CLANG_WARN_BOOL_CONVERSION = YES; 140 | CLANG_WARN_COMMA = YES; 141 | CLANG_WARN_CONSTANT_CONVERSION = YES; 142 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 143 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 144 | CLANG_WARN_EMPTY_BODY = YES; 145 | CLANG_WARN_ENUM_CONVERSION = YES; 146 | CLANG_WARN_INFINITE_RECURSION = YES; 147 | CLANG_WARN_INT_CONVERSION = YES; 148 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 149 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 150 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 151 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 152 | CLANG_WARN_STRICT_PROTOTYPES = YES; 153 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 154 | CLANG_WARN_UNREACHABLE_CODE = YES; 155 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 156 | CODE_SIGN_IDENTITY = "-"; 157 | COPY_PHASE_STRIP = NO; 158 | DEBUG_INFORMATION_FORMAT = dwarf; 159 | ENABLE_STRICT_OBJC_MSGSEND = YES; 160 | ENABLE_TESTABILITY = YES; 161 | GCC_C_LANGUAGE_STANDARD = gnu99; 162 | GCC_DYNAMIC_NO_PIC = NO; 163 | GCC_NO_COMMON_BLOCKS = YES; 164 | GCC_OPTIMIZATION_LEVEL = 0; 165 | GCC_PREPROCESSOR_DEFINITIONS = ( 166 | "DEBUG=1", 167 | "$(inherited)", 168 | ); 169 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 170 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 171 | GCC_WARN_UNDECLARED_SELECTOR = YES; 172 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 173 | GCC_WARN_UNUSED_FUNCTION = YES; 174 | GCC_WARN_UNUSED_VARIABLE = YES; 175 | MACOSX_DEPLOYMENT_TARGET = 10.12; 176 | MTL_ENABLE_DEBUG_INFO = YES; 177 | ONLY_ACTIVE_ARCH = YES; 178 | SDKROOT = macosx; 179 | }; 180 | name = Debug; 181 | }; 182 | 24934DE51F0CD64800958090 /* Release */ = { 183 | isa = XCBuildConfiguration; 184 | buildSettings = { 185 | ALWAYS_SEARCH_USER_PATHS = NO; 186 | CLANG_ANALYZER_NONNULL = YES; 187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 188 | CLANG_CXX_LIBRARY = "libc++"; 189 | CLANG_ENABLE_MODULES = YES; 190 | CLANG_ENABLE_OBJC_ARC = YES; 191 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 192 | CLANG_WARN_BOOL_CONVERSION = YES; 193 | CLANG_WARN_COMMA = YES; 194 | CLANG_WARN_CONSTANT_CONVERSION = YES; 195 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 196 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 197 | CLANG_WARN_EMPTY_BODY = YES; 198 | CLANG_WARN_ENUM_CONVERSION = YES; 199 | CLANG_WARN_INFINITE_RECURSION = YES; 200 | CLANG_WARN_INT_CONVERSION = YES; 201 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 202 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 204 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 205 | CLANG_WARN_STRICT_PROTOTYPES = YES; 206 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 207 | CLANG_WARN_UNREACHABLE_CODE = YES; 208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 209 | CODE_SIGN_IDENTITY = "-"; 210 | COPY_PHASE_STRIP = NO; 211 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 212 | ENABLE_NS_ASSERTIONS = NO; 213 | ENABLE_STRICT_OBJC_MSGSEND = YES; 214 | GCC_C_LANGUAGE_STANDARD = gnu99; 215 | GCC_NO_COMMON_BLOCKS = YES; 216 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 217 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 218 | GCC_WARN_UNDECLARED_SELECTOR = YES; 219 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 220 | GCC_WARN_UNUSED_FUNCTION = YES; 221 | GCC_WARN_UNUSED_VARIABLE = YES; 222 | MACOSX_DEPLOYMENT_TARGET = 10.12; 223 | MTL_ENABLE_DEBUG_INFO = NO; 224 | SDKROOT = macosx; 225 | }; 226 | name = Release; 227 | }; 228 | 24934DE71F0CD64800958090 /* Debug */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | PRODUCT_NAME = "$(TARGET_NAME)"; 232 | }; 233 | name = Debug; 234 | }; 235 | 24934DE81F0CD64800958090 /* Release */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | PRODUCT_NAME = "$(TARGET_NAME)"; 239 | }; 240 | name = Release; 241 | }; 242 | /* End XCBuildConfiguration section */ 243 | 244 | /* Begin XCConfigurationList section */ 245 | 24934DDA1F0CD64800958090 /* Build configuration list for PBXProject "KLGenerateSpamCode" */ = { 246 | isa = XCConfigurationList; 247 | buildConfigurations = ( 248 | 24934DE41F0CD64800958090 /* Debug */, 249 | 24934DE51F0CD64800958090 /* Release */, 250 | ); 251 | defaultConfigurationIsVisible = 0; 252 | defaultConfigurationName = Release; 253 | }; 254 | 24934DE61F0CD64800958090 /* Build configuration list for PBXNativeTarget "KLGenerateSpamCode" */ = { 255 | isa = XCConfigurationList; 256 | buildConfigurations = ( 257 | 24934DE71F0CD64800958090 /* Debug */, 258 | 24934DE81F0CD64800958090 /* Release */, 259 | ); 260 | defaultConfigurationIsVisible = 0; 261 | defaultConfigurationName = Release; 262 | }; 263 | /* End XCConfigurationList section */ 264 | }; 265 | rootObject = 24934DD71F0CD64800958090 /* Project object */; 266 | } 267 | -------------------------------------------------------------------------------- /KLGenerateSpamCode.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /KLGenerateSpamCode/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // generateSpamCode 4 | // 5 | // Created by 柯磊 on 2017/7/5. 6 | // Copyright © 2017年 GAEA. All rights reserved. 7 | // 8 | 9 | #import 10 | #include 11 | 12 | // 命令行修改工程目录下所有 png 资源 hash 值 13 | // 使用 ImageMagick 进行图片压缩,所以需要安装 ImageMagick,安装方法 brew install imagemagick 14 | // find . -iname "*.png" -exec echo {} \; -exec convert {} {} \; 15 | // or 16 | // find . -iname "*.png" -exec echo {} \; -exec convert {} -quality 95 {} \; 17 | 18 | typedef NS_ENUM(NSInteger, GSCSourceType) { 19 | GSCSourceTypeClass, 20 | GSCSourceTypeCategory, 21 | }; 22 | 23 | void recursiveDirectory(NSString *directory, NSArray *ignoreDirNames, void(^handleMFile)(NSString *mFilePath), void(^handleSwiftFile)(NSString *swiftFilePath)); 24 | void generateSpamCodeFile(NSString *outDirectory, NSString *mFilePath, GSCSourceType type); 25 | void generateSwiftSpamCodeFile(NSString *outDirectory, NSString *swiftFilePath); 26 | NSString *randomString(NSInteger length); 27 | void handleXcassetsFiles(NSString *directory); 28 | void deleteComments(NSString *directory); 29 | void modifyProjectName(NSString *projectDir, NSString *oldName, NSString *newName); 30 | void modifyClassNamePrefix(NSMutableString *projectContent, NSString *sourceCodeDir, NSArray *ignoreDirNames, NSString *oldName, NSString *newName); 31 | 32 | NSString *gOutParameterName = nil; 33 | NSString *gSourceCodeDir = nil; 34 | 35 | #pragma mark - 公共方法 36 | 37 | static const NSString *kRandomAlphabet = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 38 | NSString *randomString(NSInteger length) { 39 | NSMutableString *ret = [NSMutableString stringWithCapacity:length]; 40 | for (int i = 0; i < length; i++) { 41 | [ret appendFormat:@"%C", [kRandomAlphabet characterAtIndex:arc4random_uniform((uint32_t)[kRandomAlphabet length])]]; 42 | } 43 | return ret; 44 | } 45 | 46 | NSRange getOutermostCurlyBraceRange(NSString *string, unichar beginChar, unichar endChar, NSInteger beginIndex) { 47 | NSInteger braceCount = -1; 48 | NSInteger endIndex = string.length - 1; 49 | for (NSInteger i = beginIndex; i <= endIndex; i++) { 50 | unichar c = [string characterAtIndex:i]; 51 | if (c == beginChar) { 52 | braceCount = ((braceCount == -1) ? 0 : braceCount) + 1; 53 | } else if (c == endChar) { 54 | braceCount--; 55 | } 56 | if (braceCount == 0) { 57 | endIndex = i; 58 | break; 59 | } 60 | } 61 | return NSMakeRange(beginIndex + 1, endIndex - beginIndex - 1); 62 | } 63 | 64 | NSString * getSwiftImportString(NSString *string) { 65 | NSMutableString *ret = [NSMutableString string]; 66 | 67 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"^ *import *.+" options:NSRegularExpressionAnchorsMatchLines|NSRegularExpressionUseUnicodeWordBoundaries error:nil]; 68 | 69 | NSArray *matches = [expression matchesInString:string options:0 range:NSMakeRange(0, string.length)]; 70 | [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 71 | NSString *importRow = [string substringWithRange:obj.range]; 72 | [ret appendString:importRow]; 73 | [ret appendString:@"\n"]; 74 | }]; 75 | 76 | return ret; 77 | } 78 | 79 | BOOL regularReplacement(NSMutableString *originalString, NSString *regularExpression, NSString *newString) { 80 | __block BOOL isChanged = NO; 81 | BOOL isGroupNo1 = [newString isEqualToString:@"\\1"]; 82 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:regularExpression options:NSRegularExpressionAnchorsMatchLines|NSRegularExpressionUseUnixLineSeparators error:nil]; 83 | NSArray *matches = [expression matchesInString:originalString options:0 range:NSMakeRange(0, originalString.length)]; 84 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 85 | if (!isChanged) { 86 | isChanged = YES; 87 | } 88 | if (isGroupNo1) { 89 | NSString *withString = [originalString substringWithRange:[obj rangeAtIndex:1]]; 90 | [originalString replaceCharactersInRange:obj.range withString:withString]; 91 | } else { 92 | [originalString replaceCharactersInRange:obj.range withString:newString]; 93 | } 94 | }]; 95 | return isChanged; 96 | } 97 | 98 | void renameFile(NSString *oldPath, NSString *newPath) { 99 | NSError *error; 100 | [[NSFileManager defaultManager] moveItemAtPath:oldPath toPath:newPath error:&error]; 101 | if (error) { 102 | printf("修改文件名称失败。\n oldPath=%s\n newPath=%s\n ERROR:%s\n", oldPath.UTF8String, newPath.UTF8String, error.localizedDescription.UTF8String); 103 | abort(); 104 | } 105 | } 106 | 107 | #pragma mark - 主入口 108 | 109 | int main(int argc, const char * argv[]) { 110 | @autoreleasepool { 111 | NSArray *arguments = [[NSProcessInfo processInfo] arguments]; 112 | if (!arguments || arguments.count <= 1) { 113 | printf("缺少工程目录参数\n"); 114 | return 1; 115 | } 116 | if (arguments.count <= 2) { 117 | printf("缺少任务参数 -spamCodeOut or -handleXcassets or -deleteComments\n"); 118 | return 1; 119 | } 120 | 121 | BOOL isDirectory = NO; 122 | NSString *outDirString = nil; 123 | NSArray *ignoreDirNames = nil; 124 | BOOL needHandleXcassets = NO; 125 | BOOL needDeleteComments = NO; 126 | NSString *oldProjectName = nil; 127 | NSString *newProjectName = nil; 128 | NSString *projectFilePath = nil; 129 | NSString *oldClassNamePrefix = nil; 130 | NSString *newClassNamePrefix = nil; 131 | 132 | NSFileManager *fm = [NSFileManager defaultManager]; 133 | for (NSInteger i = 1; i < arguments.count; i++) { 134 | NSString *argument = arguments[i]; 135 | if (i == 1) { 136 | gSourceCodeDir = argument; 137 | if (![fm fileExistsAtPath:gSourceCodeDir isDirectory:&isDirectory]) { 138 | printf("%s不存在\n", [gSourceCodeDir UTF8String]); 139 | return 1; 140 | } 141 | if (!isDirectory) { 142 | printf("%s不是目录\n", [gSourceCodeDir UTF8String]); 143 | return 1; 144 | } 145 | continue; 146 | } 147 | 148 | if ([argument isEqualToString:@"-handleXcassets"]) { 149 | needHandleXcassets = YES; 150 | continue; 151 | } 152 | if ([argument isEqualToString:@"-deleteComments"]) { 153 | needDeleteComments = YES; 154 | continue; 155 | } 156 | if ([argument isEqualToString:@"-modifyProjectName"]) { 157 | NSString *string = arguments[++i]; 158 | NSArray *names = [string componentsSeparatedByString:@">"]; 159 | if (names.count < 2) { 160 | printf("修改工程名参数错误。参数示例:CCApp>DDApp,传入参数:%s\n", string.UTF8String); 161 | return 1; 162 | } 163 | oldProjectName = names[0]; 164 | newProjectName = names[1]; 165 | if (oldProjectName.length <= 0 || newProjectName.length <= 0) { 166 | printf("修改工程名参数错误。参数示例:CCApp>DDApp,传入参数:%s\n", string.UTF8String); 167 | return 1; 168 | } 169 | continue; 170 | } 171 | if ([argument isEqualToString:@"-modifyClassNamePrefix"]) { 172 | NSString *string = arguments[++i]; 173 | projectFilePath = [string stringByAppendingPathComponent:@"project.pbxproj"]; 174 | if (![fm fileExistsAtPath:string isDirectory:&isDirectory] || !isDirectory 175 | || ![fm fileExistsAtPath:projectFilePath isDirectory:&isDirectory] || isDirectory) { 176 | printf("修改类名前缀的工程文件参数错误。%s", string.UTF8String); 177 | return 1; 178 | } 179 | 180 | string = arguments[++i]; 181 | NSArray *names = [string componentsSeparatedByString:@">"]; 182 | if (names.count < 2) { 183 | printf("修改类名前缀参数错误。参数示例:CC>DD,传入参数:%s\n", string.UTF8String); 184 | return 1; 185 | } 186 | oldClassNamePrefix = names[0]; 187 | newClassNamePrefix = names[1]; 188 | if (oldClassNamePrefix.length <= 0 || newClassNamePrefix.length <= 0) { 189 | printf("修改类名前缀参数错误。参数示例:CC>DD,传入参数:%s\n", string.UTF8String); 190 | return 1; 191 | } 192 | continue; 193 | } 194 | if ([argument isEqualToString:@"-spamCodeOut"]) { 195 | outDirString = arguments[++i]; 196 | if ([fm fileExistsAtPath:outDirString isDirectory:&isDirectory]) { 197 | if (!isDirectory) { 198 | printf("%s 已存在但不是文件夹,需要传入一个输出文件夹目录\n", [outDirString UTF8String]); 199 | return 1; 200 | } 201 | } else { 202 | NSError *error = nil; 203 | if (![fm createDirectoryAtPath:outDirString withIntermediateDirectories:YES attributes:nil error:&error]) { 204 | printf("创建输出目录失败,请确认 -spamCodeOut 之后接的是一个“输出文件夹目录”参数,错误信息如下:\n传入的输出文件夹目录:%s\n%s\n", [outDirString UTF8String], [error.localizedDescription UTF8String]); 205 | return 1; 206 | } 207 | } 208 | 209 | i++; 210 | if (i < arguments.count) { 211 | gOutParameterName = arguments[i]; 212 | NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[a-zA-Z]+" options:0 error:nil]; 213 | if ([regex numberOfMatchesInString:gOutParameterName options:0 range:NSMakeRange(0, gOutParameterName.length)] <= 0) { 214 | printf("缺少垃圾代码参数名,或参数名\"%s\"不合法(需要字母开头)\n", [gOutParameterName UTF8String]); 215 | return 1; 216 | } 217 | } else { 218 | printf("缺少垃圾代码参数名,参数名需要根在输出目录后面\n"); 219 | return 1; 220 | } 221 | 222 | continue; 223 | } 224 | if ([argument isEqualToString:@"-ignoreDirNames"]) { 225 | ignoreDirNames = [arguments[++i] componentsSeparatedByString:@","]; 226 | continue; 227 | } 228 | } 229 | 230 | if (needHandleXcassets) { 231 | @autoreleasepool { 232 | handleXcassetsFiles(gSourceCodeDir); 233 | } 234 | printf("修改 Xcassets 中的图片名称完成\n"); 235 | } 236 | if (needDeleteComments) { 237 | @autoreleasepool { 238 | deleteComments(gSourceCodeDir); 239 | } 240 | printf("删除注释和空行完成\n"); 241 | } 242 | if (oldProjectName && newProjectName) { 243 | @autoreleasepool { 244 | NSString *dir = gSourceCodeDir.stringByDeletingLastPathComponent; 245 | modifyProjectName(dir, oldProjectName, newProjectName); 246 | } 247 | printf("修改工程名完成\n"); 248 | } 249 | if (oldClassNamePrefix && newClassNamePrefix) { 250 | printf("开始修改类名前缀...\n"); 251 | @autoreleasepool { 252 | // 打开工程文件 253 | NSError *error = nil; 254 | NSMutableString *projectContent = [NSMutableString stringWithContentsOfFile:projectFilePath encoding:NSUTF8StringEncoding error:&error]; 255 | if (error) { 256 | printf("打开工程文件 %s 失败:%s\n", projectFilePath.UTF8String, error.localizedDescription.UTF8String); 257 | return 1; 258 | } 259 | 260 | modifyClassNamePrefix(projectContent, gSourceCodeDir, ignoreDirNames, oldClassNamePrefix, newClassNamePrefix); 261 | 262 | [projectContent writeToFile:projectFilePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 263 | } 264 | printf("修改类名前缀完成\n"); 265 | } 266 | if (outDirString) { 267 | recursiveDirectory(gSourceCodeDir, ignoreDirNames, ^(NSString *mFilePath) { 268 | @autoreleasepool { 269 | generateSpamCodeFile(outDirString, mFilePath, GSCSourceTypeClass); 270 | generateSpamCodeFile(outDirString, mFilePath, GSCSourceTypeCategory); 271 | } 272 | }, ^(NSString *swiftFilePath) { 273 | @autoreleasepool { 274 | generateSwiftSpamCodeFile(outDirString, swiftFilePath); 275 | } 276 | }); 277 | printf("生成垃圾代码完成\n"); 278 | } 279 | } 280 | return 0; 281 | } 282 | 283 | #pragma mark - 生成垃圾代码 284 | 285 | void recursiveDirectory(NSString *directory, NSArray *ignoreDirNames, void(^handleMFile)(NSString *mFilePath), void(^handleSwiftFile)(NSString *swiftFilePath)) { 286 | NSFileManager *fm = [NSFileManager defaultManager]; 287 | NSArray *files = [fm contentsOfDirectoryAtPath:directory error:nil]; 288 | BOOL isDirectory; 289 | for (NSString *filePath in files) { 290 | NSString *path = [directory stringByAppendingPathComponent:filePath]; 291 | if ([fm fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory) { 292 | if (![ignoreDirNames containsObject:filePath]) { 293 | recursiveDirectory(path, nil, handleMFile, handleSwiftFile); 294 | } 295 | continue; 296 | } 297 | NSString *fileName = filePath.lastPathComponent; 298 | if ([fileName hasSuffix:@".h"]) { 299 | fileName = [fileName stringByDeletingPathExtension]; 300 | 301 | NSString *mFileName = [fileName stringByAppendingPathExtension:@"mm"]; 302 | if ([files containsObject:mFileName]) { 303 | handleMFile([directory stringByAppendingPathComponent:mFileName]); 304 | } 305 | } else if ([fileName hasSuffix:@".swift"]) { 306 | handleSwiftFile([directory stringByAppendingPathComponent:fileName]); 307 | } 308 | } 309 | } 310 | 311 | NSString * getImportString(NSString *hFileContent, NSString *mFileContent) { 312 | NSMutableString *ret = [NSMutableString string]; 313 | 314 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"^ *[@#]import *.+" options:NSRegularExpressionAnchorsMatchLines|NSRegularExpressionUseUnicodeWordBoundaries error:nil]; 315 | 316 | NSArray *matches = [expression matchesInString:hFileContent options:0 range:NSMakeRange(0, hFileContent.length)]; 317 | [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 318 | NSString *importRow = [hFileContent substringWithRange:[obj rangeAtIndex:0]]; 319 | [ret appendString:importRow]; 320 | [ret appendString:@"\n"]; 321 | }]; 322 | 323 | matches = [expression matchesInString:mFileContent options:0 range:NSMakeRange(0, mFileContent.length)]; 324 | [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 325 | NSString *importRow = [mFileContent substringWithRange:[obj rangeAtIndex:0]]; 326 | [ret appendString:importRow]; 327 | [ret appendString:@"\n"]; 328 | }]; 329 | 330 | return ret; 331 | } 332 | 333 | static NSString *const kHClassFileTemplate = @"\ 334 | %@\n\ 335 | @interface %@ (%@)\n\ 336 | %@\n\ 337 | @end\n"; 338 | static NSString *const kMClassFileTemplate = @"\ 339 | #import \"%@+%@.h\"\n\ 340 | @implementation %@ (%@)\n\ 341 | %@\n\ 342 | @end\n"; 343 | void generateSpamCodeFile(NSString *outDirectory, NSString *mFilePath, GSCSourceType type) { 344 | NSString *mFileContent = [NSString stringWithContentsOfFile:mFilePath encoding:NSUTF8StringEncoding error:nil]; 345 | NSString *regexStr; 346 | switch (type) { 347 | case GSCSourceTypeClass: 348 | regexStr = @" *@implementation +(\\w+)[^(]*\\n(?:.|\\n)+?@end"; 349 | break; 350 | case GSCSourceTypeCategory: 351 | regexStr = @" *@implementation *(\\w+) *\\((\\w+)\\)(?:.|\\n)+?@end"; 352 | break; 353 | } 354 | 355 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionUseUnicodeWordBoundaries error:nil]; 356 | NSArray *matches = [expression matchesInString:mFileContent options:0 range:NSMakeRange(0, mFileContent.length)]; 357 | if (matches.count <= 0) return; 358 | 359 | NSString *hFilePath = [mFilePath.stringByDeletingPathExtension stringByAppendingPathExtension:@"h"]; 360 | NSString *hFileContent = [NSString stringWithContentsOfFile:hFilePath encoding:NSUTF8StringEncoding error:nil]; 361 | 362 | // 准备要引入的文件 363 | NSString *importString = getImportString(hFileContent, mFileContent); 364 | 365 | [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull impResult, NSUInteger idx, BOOL * _Nonnull stop) { 366 | NSString *className = [mFileContent substringWithRange:[impResult rangeAtIndex:1]]; 367 | NSString *categoryName = nil; 368 | if (impResult.numberOfRanges >= 3) { 369 | categoryName = [mFileContent substringWithRange:[impResult rangeAtIndex:2]]; 370 | } 371 | 372 | if (type == GSCSourceTypeClass) { 373 | // 如果该类型没有公开,只在 .m 文件中使用,则不处理 374 | NSString *regexStr = [NSString stringWithFormat:@"\\b%@\\b", className]; 375 | NSRange range = [hFileContent rangeOfString:regexStr options:NSRegularExpressionSearch]; 376 | if (range.location == NSNotFound) { 377 | return; 378 | } 379 | } 380 | 381 | // 查找方法 382 | NSString *implementation = [mFileContent substringWithRange:impResult.range]; 383 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"^ *([-+])[^)]+\\)([^;{]+)" options:NSRegularExpressionAnchorsMatchLines|NSRegularExpressionUseUnicodeWordBoundaries error:nil]; 384 | NSArray *matches = [expression matchesInString:implementation options:0 range:NSMakeRange(0, implementation.length)]; 385 | if (matches.count <= 0) return; 386 | 387 | // 生成 h m 垃圾文件内容 388 | NSMutableString *hFileMethodsString = [NSMutableString string]; 389 | NSMutableString *mFileMethodsString = [NSMutableString string]; 390 | [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull matche, NSUInteger idx, BOOL * _Nonnull stop) { 391 | NSString *symbol = [implementation substringWithRange:[matche rangeAtIndex:1]]; 392 | NSString *methodName = [[implementation substringWithRange:[matche rangeAtIndex:2]] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 393 | if ([methodName containsString:@":"]) { 394 | methodName = [methodName stringByAppendingFormat:@" %@:(NSString *)%@", gOutParameterName, gOutParameterName]; 395 | } else { 396 | methodName = [methodName stringByAppendingFormat:@"%@:(NSString *)%@", gOutParameterName.capitalizedString, gOutParameterName]; 397 | } 398 | 399 | [hFileMethodsString appendFormat:@"%@ (void)%@;\n", symbol, methodName]; 400 | 401 | [mFileMethodsString appendFormat:@"%@ (void)%@ {\n", symbol, methodName]; 402 | [mFileMethodsString appendFormat:@" NSLog(@\"%%@\", %@);\n", gOutParameterName]; 403 | [mFileMethodsString appendString:@"}\n"]; 404 | }]; 405 | 406 | NSString *newCategoryName; 407 | switch (type) { 408 | case GSCSourceTypeClass: 409 | newCategoryName = gOutParameterName.capitalizedString; 410 | break; 411 | case GSCSourceTypeCategory: 412 | newCategoryName = [NSString stringWithFormat:@"%@%@", categoryName, gOutParameterName.capitalizedString]; 413 | break; 414 | } 415 | 416 | NSString *fileName = [NSString stringWithFormat:@"%@+%@.h", className, newCategoryName]; 417 | NSString *fileContent = [NSString stringWithFormat:kHClassFileTemplate, importString, className, newCategoryName, hFileMethodsString]; 418 | [fileContent writeToFile:[outDirectory stringByAppendingPathComponent:fileName] atomically:YES encoding:NSUTF8StringEncoding error:nil]; 419 | 420 | fileName = [NSString stringWithFormat:@"%@+%@.mm", className, newCategoryName]; 421 | fileContent = [NSString stringWithFormat:kMClassFileTemplate, className, newCategoryName, className, newCategoryName, mFileMethodsString]; 422 | // NSString *inputDirectory = [[mFilePath stringByDeletingLastPathComponent] lastPathComponent]; 423 | // if (![inputDirectory isEqual: @"Classes"]) 424 | // { 425 | // NSLog(@"not classes: %@", inputDirectory); 426 | // outDirectory = [outDirectory stringByAppendingPathComponent:inputDirectory]; 427 | // } 428 | [fileContent writeToFile:[outDirectory stringByAppendingPathComponent:fileName] atomically:YES encoding:NSUTF8StringEncoding error:nil]; 429 | }]; 430 | } 431 | 432 | static NSString *const kSwiftFileTemplate = @"\ 433 | %@\n\ 434 | extension %@ {\n%@\ 435 | }\n"; 436 | static NSString *const kSwiftMethodTemplate = @"\ 437 | func %@%@(_ %@: String%@) {\n\ 438 | print(%@)\n\ 439 | }\n"; 440 | void generateSwiftSpamCodeFile(NSString *outDirectory, NSString *swiftFilePath) { 441 | NSString *swiftFileContent = [NSString stringWithContentsOfFile:swiftFilePath encoding:NSUTF8StringEncoding error:nil]; 442 | 443 | // 查找 class 声明 444 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@" *(class|struct) +(\\w+)[^{]+" options:NSRegularExpressionUseUnicodeWordBoundaries error:nil]; 445 | NSArray *matches = [expression matchesInString:swiftFileContent options:0 range:NSMakeRange(0, swiftFileContent.length)]; 446 | if (matches.count <= 0) return; 447 | 448 | NSString *fileImportStrings = getSwiftImportString(swiftFileContent); 449 | __block NSInteger braceEndIndex = 0; 450 | [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull classResult, NSUInteger idx, BOOL * _Nonnull stop) { 451 | // 已经处理到该 range 后面去了,过掉 452 | NSInteger matchEndIndex = classResult.range.location + classResult.range.length; 453 | if (matchEndIndex < braceEndIndex) return; 454 | // 是 class 方法,过掉 455 | NSString *fullMatchString = [swiftFileContent substringWithRange:classResult.range]; 456 | if ([fullMatchString containsString:@"("]) return; 457 | 458 | NSRange braceRange = getOutermostCurlyBraceRange(swiftFileContent, '{', '}', matchEndIndex); 459 | braceEndIndex = braceRange.location + braceRange.length; 460 | 461 | // 查找方法 462 | NSString *classContent = [swiftFileContent substringWithRange:braceRange]; 463 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:@"func +([^(]+)\\([^{]+" options:NSRegularExpressionUseUnicodeWordBoundaries error:nil]; 464 | NSArray *matches = [expression matchesInString:classContent options:0 range:NSMakeRange(0, classContent.length)]; 465 | if (matches.count <= 0) return; 466 | 467 | NSMutableString *methodsString = [NSMutableString string]; 468 | [matches enumerateObjectsUsingBlock:^(NSTextCheckingResult * _Nonnull funcResult, NSUInteger idx, BOOL * _Nonnull stop) { 469 | NSRange funcNameRange = [funcResult rangeAtIndex:1]; 470 | NSString *funcName = [classContent substringWithRange:funcNameRange]; 471 | NSRange oldParameterRange = getOutermostCurlyBraceRange(classContent, '(', ')', funcNameRange.location + funcNameRange.length); 472 | NSString *oldParameterName = [classContent substringWithRange:oldParameterRange]; 473 | oldParameterName = [oldParameterName stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 474 | if (oldParameterName.length > 0) { 475 | oldParameterName = [@", " stringByAppendingString:oldParameterName]; 476 | } 477 | if (![funcName containsString:@"<"] && ![funcName containsString:@">"]) { 478 | funcName = [NSString stringWithFormat:@"%@%@", funcName, randomString(5)]; 479 | [methodsString appendFormat:kSwiftMethodTemplate, funcName, gOutParameterName.capitalizedString, gOutParameterName, oldParameterName, gOutParameterName]; 480 | } else { 481 | NSLog(@"string contains `[` or `]` bla! funcName: %@", funcName); 482 | } 483 | }]; 484 | if (methodsString.length <= 0) return; 485 | 486 | NSString *className = [swiftFileContent substringWithRange:[classResult rangeAtIndex:2]]; 487 | 488 | NSString *fileName = [NSString stringWithFormat:@"%@%@Ext.swift", className, gOutParameterName.capitalizedString]; 489 | NSString *filePath = [outDirectory stringByAppendingPathComponent:fileName]; 490 | NSString *fileContent = @""; 491 | if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { 492 | fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; 493 | } 494 | fileContent = [fileContent stringByAppendingFormat:kSwiftFileTemplate, fileImportStrings, className, methodsString]; 495 | [fileContent writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 496 | }]; 497 | } 498 | 499 | #pragma mark - 处理 Xcassets 中的图片文件 500 | 501 | void handleXcassetsFiles(NSString *directory) { 502 | NSFileManager *fm = [NSFileManager defaultManager]; 503 | NSArray *files = [fm contentsOfDirectoryAtPath:directory error:nil]; 504 | BOOL isDirectory; 505 | for (NSString *fileName in files) { 506 | NSString *filePath = [directory stringByAppendingPathComponent:fileName]; 507 | if ([fm fileExistsAtPath:filePath isDirectory:&isDirectory] && isDirectory) { 508 | handleXcassetsFiles(filePath); 509 | continue; 510 | } 511 | if (![fileName isEqualToString:@"Contents.json"]) continue; 512 | NSString *contentsDirectoryName = filePath.stringByDeletingLastPathComponent.lastPathComponent; 513 | if (![contentsDirectoryName hasSuffix:@".imageset"]) continue; 514 | 515 | NSString *fileContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; 516 | if (!fileContent) continue; 517 | 518 | NSMutableArray *processedImageFileNameArray = @[].mutableCopy; 519 | static NSString * const regexStr = @"\"filename\" *: *\"(.*)?\""; 520 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionUseUnicodeWordBoundaries error:nil]; 521 | NSArray *matches = [expression matchesInString:fileContent options:0 range:NSMakeRange(0, fileContent.length)]; 522 | while (matches.count > 0) { 523 | NSInteger i = 0; 524 | NSString *imageFileName = nil; 525 | do { 526 | if (i >= matches.count) { 527 | i = -1; 528 | break; 529 | } 530 | imageFileName = [fileContent substringWithRange:[matches[i] rangeAtIndex:1]]; 531 | i++; 532 | } while ([processedImageFileNameArray containsObject:imageFileName]); 533 | if (i < 0) break; 534 | 535 | NSString *imageFilePath = [filePath.stringByDeletingLastPathComponent stringByAppendingPathComponent:imageFileName]; 536 | if ([fm fileExistsAtPath:imageFilePath]) { 537 | NSString *newImageFileName = [randomString(10) stringByAppendingPathExtension:imageFileName.pathExtension]; 538 | NSString *newImageFilePath = [filePath.stringByDeletingLastPathComponent stringByAppendingPathComponent:newImageFileName]; 539 | while ([fm fileExistsAtPath:newImageFileName]) { 540 | newImageFileName = [randomString(10) stringByAppendingPathExtension:imageFileName.pathExtension]; 541 | newImageFilePath = [filePath.stringByDeletingLastPathComponent stringByAppendingPathComponent:newImageFileName]; 542 | } 543 | 544 | renameFile(imageFilePath, newImageFilePath); 545 | 546 | fileContent = [fileContent stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"\"%@\"", imageFileName] 547 | withString:[NSString stringWithFormat:@"\"%@\"", newImageFileName]]; 548 | [fileContent writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 549 | 550 | [processedImageFileNameArray addObject:newImageFileName]; 551 | } else { 552 | [processedImageFileNameArray addObject:imageFileName]; 553 | } 554 | 555 | matches = [expression matchesInString:fileContent options:0 range:NSMakeRange(0, fileContent.length)]; 556 | } 557 | } 558 | } 559 | 560 | #pragma mark - 删除注释 561 | 562 | void deleteComments(NSString *directory) { 563 | NSFileManager *fm = [NSFileManager defaultManager]; 564 | NSArray *files = [fm contentsOfDirectoryAtPath:directory error:nil]; 565 | BOOL isDirectory; 566 | for (NSString *fileName in files) { 567 | NSString *filePath = [directory stringByAppendingPathComponent:fileName]; 568 | if ([fm fileExistsAtPath:filePath isDirectory:&isDirectory] && isDirectory) { 569 | deleteComments(filePath); 570 | continue; 571 | } 572 | if (![fileName hasSuffix:@".h"] && ![fileName hasSuffix:@".m"] && ![fileName hasSuffix:@".swift"]) continue; 573 | NSMutableString *fileContent = [NSMutableString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; 574 | regularReplacement(fileContent, @"([^:/])//.*", @"\\1"); 575 | regularReplacement(fileContent, @"^//.*", @""); 576 | regularReplacement(fileContent, @"/\\*{1,2}[\\s\\S]*?\\*/", @""); 577 | regularReplacement(fileContent, @"^\\s*\\n", @""); 578 | [fileContent writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 579 | } 580 | } 581 | 582 | #pragma mark - 修改工程名 583 | 584 | void resetEntitlementsFileName(NSString *projectPbxprojFilePath, NSString *oldName, NSString *newName) { 585 | NSString *rootPath = projectPbxprojFilePath.stringByDeletingLastPathComponent.stringByDeletingLastPathComponent; 586 | NSMutableString *fileContent = [NSMutableString stringWithContentsOfFile:projectPbxprojFilePath encoding:NSUTF8StringEncoding error:nil]; 587 | 588 | NSString *regularExpression = @"CODE_SIGN_ENTITLEMENTS = \"?([^\";]+)"; 589 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:regularExpression options:0 error:nil]; 590 | NSArray *matches = [expression matchesInString:fileContent options:0 range:NSMakeRange(0, fileContent.length)]; 591 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 592 | NSString *entitlementsPath = [fileContent substringWithRange:[obj rangeAtIndex:1]]; 593 | NSString *entitlementsName = entitlementsPath.lastPathComponent.stringByDeletingPathExtension; 594 | if (![entitlementsName isEqualToString:oldName]) return; 595 | entitlementsPath = [rootPath stringByAppendingPathComponent:entitlementsPath]; 596 | if (![[NSFileManager defaultManager] fileExistsAtPath:entitlementsPath]) return; 597 | NSString *newPath = [entitlementsPath.stringByDeletingLastPathComponent stringByAppendingPathComponent:[newName stringByAppendingPathExtension:@"entitlements"]]; 598 | renameFile(entitlementsPath, newPath); 599 | }]; 600 | } 601 | 602 | void resetBridgingHeaderFileName(NSString *projectPbxprojFilePath, NSString *oldName, NSString *newName) { 603 | NSString *rootPath = projectPbxprojFilePath.stringByDeletingLastPathComponent.stringByDeletingLastPathComponent; 604 | NSMutableString *fileContent = [NSMutableString stringWithContentsOfFile:projectPbxprojFilePath encoding:NSUTF8StringEncoding error:nil]; 605 | 606 | NSString *regularExpression = @"SWIFT_OBJC_BRIDGING_HEADER = \"?([^\";]+)"; 607 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:regularExpression options:0 error:nil]; 608 | NSArray *matches = [expression matchesInString:fileContent options:0 range:NSMakeRange(0, fileContent.length)]; 609 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 610 | NSString *entitlementsPath = [fileContent substringWithRange:[obj rangeAtIndex:1]]; 611 | NSString *entitlementsName = entitlementsPath.lastPathComponent.stringByDeletingPathExtension; 612 | if (![entitlementsName isEqualToString:oldName]) return; 613 | entitlementsPath = [rootPath stringByAppendingPathComponent:entitlementsPath]; 614 | if (![[NSFileManager defaultManager] fileExistsAtPath:entitlementsPath]) return; 615 | NSString *newPath = [entitlementsPath.stringByDeletingLastPathComponent stringByAppendingPathComponent:[newName stringByAppendingPathExtension:@"h"]]; 616 | renameFile(entitlementsPath, newPath); 617 | }]; 618 | } 619 | 620 | void replacePodfileContent(NSString *filePath, NSString *oldString, NSString *newString) { 621 | NSMutableString *fileContent = [NSMutableString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; 622 | 623 | NSString *regularExpression = [NSString stringWithFormat:@"target +'%@", oldString]; 624 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:regularExpression options:0 error:nil]; 625 | NSArray *matches = [expression matchesInString:fileContent options:0 range:NSMakeRange(0, fileContent.length)]; 626 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 627 | [fileContent replaceCharactersInRange:obj.range withString:[NSString stringWithFormat:@"target '%@", newString]]; 628 | }]; 629 | 630 | regularExpression = [NSString stringWithFormat:@"project +'%@.", oldString]; 631 | expression = [NSRegularExpression regularExpressionWithPattern:regularExpression options:0 error:nil]; 632 | matches = [expression matchesInString:fileContent options:0 range:NSMakeRange(0, fileContent.length)]; 633 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 634 | [fileContent replaceCharactersInRange:obj.range withString:[NSString stringWithFormat:@"project '%@.", newString]]; 635 | }]; 636 | 637 | [fileContent writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 638 | } 639 | 640 | void replaceProjectFileContent(NSString *filePath, NSString *oldString, NSString *newString) { 641 | NSMutableString *fileContent = [NSMutableString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; 642 | 643 | NSString *regularExpression = [NSString stringWithFormat:@"\\b%@\\b", oldString]; 644 | NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:regularExpression options:0 error:nil]; 645 | NSArray *matches = [expression matchesInString:fileContent options:0 range:NSMakeRange(0, fileContent.length)]; 646 | [matches enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(NSTextCheckingResult * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 647 | [fileContent replaceCharactersInRange:obj.range withString:newString]; 648 | }]; 649 | 650 | [fileContent writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; 651 | } 652 | 653 | void modifyFilesClassName(NSString *sourceCodeDir, NSString *oldClassName, NSString *newClassName); 654 | 655 | void modifyProjectName(NSString *projectDir, NSString *oldName, NSString *newName) { 656 | NSString *sourceCodeDirPath = [projectDir stringByAppendingPathComponent:oldName]; 657 | NSString *xcodeprojFilePath = [sourceCodeDirPath stringByAppendingPathExtension:@"xcodeproj"]; 658 | NSString *xcworkspaceFilePath = [sourceCodeDirPath stringByAppendingPathExtension:@"xcworkspace"]; 659 | 660 | NSFileManager *fm = [NSFileManager defaultManager]; 661 | BOOL isDirectory; 662 | 663 | // old-Swift.h > new-Swift.h 664 | modifyFilesClassName(projectDir, [oldName stringByAppendingString:@"-Swift.h"], [newName stringByAppendingString:@"-Swift.h"]); 665 | 666 | // 改 Podfile 中的工程名 667 | NSString *podfilePath = [projectDir stringByAppendingPathComponent:@"Podfile"]; 668 | if ([fm fileExistsAtPath:podfilePath isDirectory:&isDirectory] && !isDirectory) { 669 | replacePodfileContent(podfilePath, oldName, newName); 670 | } 671 | 672 | // 改工程文件内容 673 | if ([fm fileExistsAtPath:xcodeprojFilePath isDirectory:&isDirectory] && isDirectory) { 674 | // 替换 project.pbxproj 文件内容 675 | NSString *projectPbxprojFilePath = [xcodeprojFilePath stringByAppendingPathComponent:@"project.pbxproj"]; 676 | if ([fm fileExistsAtPath:projectPbxprojFilePath]) { 677 | resetBridgingHeaderFileName(projectPbxprojFilePath, [oldName stringByAppendingString:@"-Bridging-Header"], [newName stringByAppendingString:@"-Bridging-Header"]); 678 | resetEntitlementsFileName(projectPbxprojFilePath, oldName, newName); 679 | replaceProjectFileContent(projectPbxprojFilePath, oldName, newName); 680 | } 681 | // 替换 project.xcworkspace/contents.xcworkspacedata 文件内容 682 | NSString *contentsXcworkspacedataFilePath = [xcodeprojFilePath stringByAppendingPathComponent:@"project.xcworkspace/contents.xcworkspacedata"]; 683 | if ([fm fileExistsAtPath:contentsXcworkspacedataFilePath]) { 684 | replaceProjectFileContent(contentsXcworkspacedataFilePath, oldName, newName); 685 | } 686 | // xcuserdata 本地用户文件 687 | NSString *xcuserdataFilePath = [xcodeprojFilePath stringByAppendingPathComponent:@"xcuserdata"]; 688 | if ([fm fileExistsAtPath:xcuserdataFilePath]) { 689 | [fm removeItemAtPath:xcuserdataFilePath error:nil]; 690 | } 691 | // 改名工程文件 692 | renameFile(xcodeprojFilePath, [[projectDir stringByAppendingPathComponent:newName] stringByAppendingPathExtension:@"xcodeproj"]); 693 | } 694 | 695 | // 改工程组文件内容 696 | if ([fm fileExistsAtPath:xcworkspaceFilePath isDirectory:&isDirectory] && isDirectory) { 697 | // 替换 contents.xcworkspacedata 文件内容 698 | NSString *contentsXcworkspacedataFilePath = [xcworkspaceFilePath stringByAppendingPathComponent:@"contents.xcworkspacedata"]; 699 | if ([fm fileExistsAtPath:contentsXcworkspacedataFilePath]) { 700 | replaceProjectFileContent(contentsXcworkspacedataFilePath, oldName, newName); 701 | } 702 | // xcuserdata 本地用户文件 703 | NSString *xcuserdataFilePath = [xcworkspaceFilePath stringByAppendingPathComponent:@"xcuserdata"]; 704 | if ([fm fileExistsAtPath:xcuserdataFilePath]) { 705 | [fm removeItemAtPath:xcuserdataFilePath error:nil]; 706 | } 707 | // 改名工程文件 708 | renameFile(xcworkspaceFilePath, [[projectDir stringByAppendingPathComponent:newName] stringByAppendingPathExtension:@"xcworkspace"]); 709 | } 710 | 711 | // 改源代码文件夹名称 712 | if ([fm fileExistsAtPath:sourceCodeDirPath isDirectory:&isDirectory] && isDirectory) { 713 | renameFile(sourceCodeDirPath, [projectDir stringByAppendingPathComponent:newName]); 714 | } 715 | } 716 | 717 | #pragma mark - 修改类名前缀 718 | 719 | void modifyFilesClassName(NSString *sourceCodeDir, NSString *oldClassName, NSString *newClassName) { 720 | // 文件内容 Const > DDConst (h,m,swift,xib,storyboard) 721 | NSFileManager *fm = [NSFileManager defaultManager]; 722 | NSArray *files = [fm contentsOfDirectoryAtPath:sourceCodeDir error:nil]; 723 | BOOL isDirectory; 724 | for (NSString *filePath in files) { 725 | NSString *path = [sourceCodeDir stringByAppendingPathComponent:filePath]; 726 | if ([fm fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory) { 727 | modifyFilesClassName(path, oldClassName, newClassName); 728 | continue; 729 | } 730 | 731 | NSString *fileName = filePath.lastPathComponent; 732 | if ([fileName hasSuffix:@".h"] || [fileName hasSuffix:@".mm"] || [fileName hasSuffix:@".pch"] || [fileName hasSuffix:@".swift"] || [fileName hasSuffix:@".xib"] || [fileName hasSuffix:@".storyboard"]) { 733 | 734 | NSError *error = nil; 735 | NSMutableString *fileContent = [NSMutableString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; 736 | if (error) { 737 | printf("打开文件 %s 失败:%s\n", path.UTF8String, error.localizedDescription.UTF8String); 738 | abort(); 739 | } 740 | 741 | NSString *regularExpression = [NSString stringWithFormat:@"\\b%@\\b", oldClassName]; 742 | BOOL isChanged = regularReplacement(fileContent, regularExpression, newClassName); 743 | if (!isChanged) continue; 744 | error = nil; 745 | [fileContent writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error]; 746 | if (error) { 747 | printf("保存文件 %s 失败:%s\n", path.UTF8String, error.localizedDescription.UTF8String); 748 | abort(); 749 | } 750 | } 751 | } 752 | } 753 | 754 | void modifyClassNamePrefix(NSMutableString *projectContent, NSString *sourceCodeDir, NSArray *ignoreDirNames, NSString *oldName, NSString *newName) { 755 | NSFileManager *fm = [NSFileManager defaultManager]; 756 | 757 | // 遍历源代码文件 h 与 m 配对,swift 758 | NSArray *files = [fm contentsOfDirectoryAtPath:sourceCodeDir error:nil]; 759 | BOOL isDirectory; 760 | for (NSString *filePath in files) { 761 | NSString *path = [sourceCodeDir stringByAppendingPathComponent:filePath]; 762 | if ([fm fileExistsAtPath:path isDirectory:&isDirectory] && isDirectory) { 763 | if (![ignoreDirNames containsObject:filePath]) { 764 | modifyClassNamePrefix(projectContent, path, ignoreDirNames, oldName, newName); 765 | } 766 | continue; 767 | } 768 | 769 | NSString *fileName = filePath.lastPathComponent.stringByDeletingPathExtension; 770 | NSString *fileExtension = filePath.pathExtension; 771 | NSString *newClassName; 772 | if ([fileName hasPrefix:oldName]) { 773 | newClassName = [newName stringByAppendingString:[fileName substringFromIndex:oldName.length]]; 774 | } else { 775 | newClassName = [newName stringByAppendingString:fileName]; 776 | } 777 | 778 | // 文件名 Const.ext > DDConst.ext 779 | if ([fileExtension isEqualToString:@"h"]) { 780 | NSString *mFileName = [fileName stringByAppendingPathExtension:@"mm"]; 781 | if ([files containsObject:mFileName]) { 782 | NSString *oldFilePath = [[sourceCodeDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:@"h"]; 783 | NSString *newFilePath = [[sourceCodeDir stringByAppendingPathComponent:newClassName] stringByAppendingPathExtension:@"h"]; 784 | renameFile(oldFilePath, newFilePath); 785 | oldFilePath = [[sourceCodeDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:@"mm"]; 786 | newFilePath = [[sourceCodeDir stringByAppendingPathComponent:newClassName] stringByAppendingPathExtension:@"mm"]; 787 | renameFile(oldFilePath, newFilePath); 788 | oldFilePath = [[sourceCodeDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:@"xib"]; 789 | if ([fm fileExistsAtPath:oldFilePath]) { 790 | newFilePath = [[sourceCodeDir stringByAppendingPathComponent:newClassName] stringByAppendingPathExtension:@"xib"]; 791 | renameFile(oldFilePath, newFilePath); 792 | } 793 | 794 | @autoreleasepool { 795 | modifyFilesClassName(gSourceCodeDir, fileName, newClassName); 796 | } 797 | } else { 798 | continue; 799 | } 800 | } else if ([fileExtension isEqualToString:@"swift"]) { 801 | NSString *oldFilePath = [[sourceCodeDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:@"swift"]; 802 | NSString *newFilePath = [[sourceCodeDir stringByAppendingPathComponent:newClassName] stringByAppendingPathExtension:@"swift"]; 803 | renameFile(oldFilePath, newFilePath); 804 | oldFilePath = [[sourceCodeDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:@"xib"]; 805 | if ([fm fileExistsAtPath:oldFilePath]) { 806 | newFilePath = [[sourceCodeDir stringByAppendingPathComponent:newClassName] stringByAppendingPathExtension:@"xib"]; 807 | renameFile(oldFilePath, newFilePath); 808 | } 809 | 810 | @autoreleasepool { 811 | modifyFilesClassName(gSourceCodeDir, fileName.stringByDeletingPathExtension, newClassName); 812 | } 813 | } else { 814 | continue; 815 | } 816 | 817 | // 修改工程文件中的文件名 818 | NSString *regularExpression = [NSString stringWithFormat:@"\\b%@\\b", fileName]; 819 | regularReplacement(projectContent, regularExpression, newClassName); 820 | } 821 | } 822 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 柯磊 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity导出的Xcode工程垃圾代码生成器 2 | 修改自KLGenerateSpamCode 3 | https://github.com/klaus01/KLGenerateSpamCode
4 | 使其对应Unity导出的Xcode工程
5 | 懒得重新fork了,在此感谢原作者
6 | 7 | 一下为原说明
8 | 9 | # KLGenerateSpamCode 垃圾代码生成器 10 | 本工具用于应对苹果对重复应用的审核(Guideline 4.3 Design Spam),避免苹果机审检测概率。 11 | 12 | ## 主要功能 13 | 1. 修改工程名 14 | 1. 修改类名前缀 15 | 1. 扫描工程中的代码,生成同等数量的 Category 文件,文件中及是同等方法数量的垃圾代码。 16 | 1. 修改 xxx.xcassets 文件夹中的 png 资源文件名。 17 | 1. 删除代码中的所有注释和空行。 18 | 19 | ## 使用 20 | ### 使用源码 21 | 1. 下载源码。 22 | 1. 用 Xcode 打开工程并配置参数。如图![配置参数](https://github.com/klaus01/KLGenerateSpamCode/raw/master/images/p2.png) 23 | 1. 运行 24 | 25 | ### 使用二进制文件,在终端中执行 GenerateSpamCode 26 | ``` 27 | $ ./GenerateSpamCode \ 28 | /Users/kelei/Documents/work/git/projectName/source \ 29 | -deleteComments 30 | ``` 31 | 32 | ### 参数说明 33 | - *(必填)* **源码文件夹绝对路径**(如:`/Users/kelei/Documents/work/git/projectName/source`) 34 | - **-modifyProjectName [原名称]>[新名称]** 修改工程名。程序会修改`原名称-Swift.h`、`Podfile`、`原名称-Bridging-Header.h`、`源码文件夹绝对路径`、`原名称.xcodeproj`和`原名称.xcworkspace`的名称和内容。*`Podfile`被修改后需要手动`pod install`* 35 | - **-modifyClassNamePrefix [工程文件 xcodeproj 绝对路径] [原前缀]>[新前缀]** 修改源代码类名前缀。程序会扫描`源码文件夹绝对路径`下的 .h .swift 文件,修改文件名,修改使用该类名的代码,修改`工程文件`中的文件名。文件名有`原前缀`的会修改成`新前缀`,如:`原前缀ViewController`变成`新前缀ViewController`;没有`原前缀`的会增加`新前缀`,如:`ViewController`变成`新前缀ViewController`。 36 | - **-spamCodeOut [垃圾代码文件输出目录] [垃圾代码方法增加的参数名]** 生成垃圾代码。程序会扫描`源码文件夹绝对路径`下的 .m .swift 文件中的类和方法,并生成`category`和`extension`文件,文件中的方法是在代码原方法的基础上增加`垃圾代码方法增加的参数名`参数。如:`-spamCodeOut /dir AppLog`,`- (void)setupKeys {}`>`- (void)setupKeysAppLog:(NSString *)appLog {}`,`- (void)foo:(NSString *)str {}`>`- (void)foo:(NSString *)str appLog:(NSString *)appLog {}` 37 | - **-ignoreDirNames [忽略文件夹名称字符串]** 忽略这些文件夹,对`-modifyClassNamePrefix`和`-spamCodeOut`参数有效。目前只会忽略`源码文件夹绝对路径`下一级的这些目录。如:`/p/s -ignoreDirNames categorys`,那么`/p/s/categorys`会被忽略,但`/p/s/viewControllers/categorys`不会忽略。 38 | - **-handleXcassets** 修改`xxx.xcassets`文件夹中的 png 资源文件名,同时也`Contents.json`文件中的关联名称,不会影响代码中使用图片。 39 | - **-deleteComments** 删除工程目录下 .h .m .swift 文件中的注释和空行。 40 | 41 | ## 另外修改图片 hash 值的方法 42 | 使用 [ImageMagick](http://www.imagemagick.org/) 对 png 图片做轻量压缩,及不损失图片质量,又可改变图片文件 hash 值。方法: 43 | 1. 安装 ImageMagick,`brew install imagemagick` 44 | 2. 压缩工程目录下所有 png 文件,`find . -iname "*.png" -exec echo {} \; -exec convert {} {} \;` 45 | 46 | ## 使用经验 47 | 就我 2017-11 月的提交情况来看,只需要做如下修改就可以上马甲包了。 48 | 1. 修改工程名 49 | 2. 修改类名前缀 50 | 3. 修改图片文件 Hash 值 51 | 4. 修改 .xcassets 中的图片文件名 52 | 5. 用别的电脑打包 53 | 54 | ## 已知问题 55 | - 生成的垃圾代码文件可能是 .m 文件中实现的私有类,编译垃圾代码可能会报错,删除该垃圾代码 .h .m 文件及可。 56 | 57 | -------------------------------------------------------------------------------- /images/p2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glegoo/unity-objc-spam-generater/cdf9202862c02f59978f8b5be45a95041017d1d6/images/p2.png --------------------------------------------------------------------------------