├── .gitignore ├── DebuggableContext.xcworkspace └── contents.xcworkspacedata ├── DebuggableContext ├── DebuggableContext.xcodeproj │ └── project.pbxproj └── DebuggableContext │ ├── DebuggableContext.h │ ├── DebuggableContext.swift │ └── Info.plist ├── DebuggableContextDemo ├── DebuggableContextDemo.xcodeproj │ └── project.pbxproj └── DebuggableContextDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── ViewController.swift ├── LICENSE ├── README.md └── RepoAssets ├── compile-flag.png └── demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/xcode,swift,macos 3 | 4 | ### macOS ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | ### Swift ### 32 | # Xcode 33 | # 34 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 35 | 36 | ## Build generated 37 | build/ 38 | DerivedData/ 39 | 40 | ## Various settings 41 | *.pbxuser 42 | !default.pbxuser 43 | *.mode1v3 44 | !default.mode1v3 45 | *.mode2v3 46 | !default.mode2v3 47 | *.perspectivev3 48 | !default.perspectivev3 49 | xcuserdata/ 50 | 51 | ## Other 52 | *.moved-aside 53 | *.xccheckout 54 | *.xcscmblueprint 55 | 56 | ## Obj-C/Swift specific 57 | *.hmap 58 | *.ipa 59 | *.dSYM.zip 60 | *.dSYM 61 | 62 | ## Playgrounds 63 | timeline.xctimeline 64 | playground.xcworkspace 65 | 66 | # Swift Package Manager 67 | # 68 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 69 | # Packages/ 70 | # Package.pins 71 | .build/ 72 | 73 | # CocoaPods - Refactored to standalone file 74 | 75 | # Carthage - Refactored to standalone file 76 | 77 | # fastlane 78 | # 79 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 80 | # screenshots whenever they are needed. 81 | # For more information about the recommended setup visit: 82 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 83 | 84 | fastlane/report.xml 85 | fastlane/Preview.html 86 | fastlane/screenshots 87 | fastlane/test_output 88 | 89 | ### Xcode ### 90 | # Xcode 91 | # 92 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 93 | 94 | ## Build generated 95 | 96 | ## Various settings 97 | 98 | ## Other 99 | 100 | ### Xcode Patch ### 101 | *.xcodeproj/* 102 | !*.xcodeproj/project.pbxproj 103 | !*.xcodeproj/xcshareddata/ 104 | !*.xcworkspace/contents.xcworkspacedata 105 | /*.gcno 106 | 107 | 108 | # End of https://www.gitignore.io/api/xcode,swift,macos 109 | -------------------------------------------------------------------------------- /DebuggableContext.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DebuggableContext/DebuggableContext.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4BED02F9205A26070050BACF /* DebuggableContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BED02F7205A26070050BACF /* DebuggableContext.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | 4BED033C205A27330050BACF /* DebuggableContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BED033B205A27330050BACF /* DebuggableContext.swift */; }; 12 | /* End PBXBuildFile section */ 13 | 14 | /* Begin PBXFileReference section */ 15 | 4BED02F4205A26070050BACF /* DebuggableContext.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DebuggableContext.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 16 | 4BED02F7205A26070050BACF /* DebuggableContext.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DebuggableContext.h; sourceTree = ""; }; 17 | 4BED02F8205A26070050BACF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 18 | 4BED033B205A27330050BACF /* DebuggableContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggableContext.swift; sourceTree = ""; }; 19 | /* End PBXFileReference section */ 20 | 21 | /* Begin PBXFrameworksBuildPhase section */ 22 | 4BED02F0205A26070050BACF /* Frameworks */ = { 23 | isa = PBXFrameworksBuildPhase; 24 | buildActionMask = 2147483647; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXFrameworksBuildPhase section */ 30 | 31 | /* Begin PBXGroup section */ 32 | 4BED02EA205A26070050BACF = { 33 | isa = PBXGroup; 34 | children = ( 35 | 4BED02F6205A26070050BACF /* DebuggableContext */, 36 | 4BED02F5205A26070050BACF /* Products */, 37 | ); 38 | sourceTree = ""; 39 | }; 40 | 4BED02F5205A26070050BACF /* Products */ = { 41 | isa = PBXGroup; 42 | children = ( 43 | 4BED02F4205A26070050BACF /* DebuggableContext.framework */, 44 | ); 45 | name = Products; 46 | sourceTree = ""; 47 | }; 48 | 4BED02F6205A26070050BACF /* DebuggableContext */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 4BED033B205A27330050BACF /* DebuggableContext.swift */, 52 | 4BED02F7205A26070050BACF /* DebuggableContext.h */, 53 | 4BED02F8205A26070050BACF /* Info.plist */, 54 | ); 55 | path = DebuggableContext; 56 | sourceTree = ""; 57 | }; 58 | /* End PBXGroup section */ 59 | 60 | /* Begin PBXHeadersBuildPhase section */ 61 | 4BED02F1205A26070050BACF /* Headers */ = { 62 | isa = PBXHeadersBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | 4BED02F9205A26070050BACF /* DebuggableContext.h in Headers */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXHeadersBuildPhase section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | 4BED02F3205A26070050BACF /* DebuggableContext */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = 4BED02FC205A26070050BACF /* Build configuration list for PBXNativeTarget "DebuggableContext" */; 75 | buildPhases = ( 76 | 4BED02EF205A26070050BACF /* Sources */, 77 | 4BED02F0205A26070050BACF /* Frameworks */, 78 | 4BED02F1205A26070050BACF /* Headers */, 79 | 4BED02F2205A26070050BACF /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = DebuggableContext; 86 | productName = DebuggableContext; 87 | productReference = 4BED02F4205A26070050BACF /* DebuggableContext.framework */; 88 | productType = "com.apple.product-type.framework"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | 4BED02EB205A26070050BACF /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastUpgradeCheck = 0920; 97 | ORGANIZATIONNAME = "OneV's Den"; 98 | TargetAttributes = { 99 | 4BED02F3205A26070050BACF = { 100 | CreatedOnToolsVersion = 9.2; 101 | LastSwiftMigration = 0920; 102 | ProvisioningStyle = Automatic; 103 | }; 104 | }; 105 | }; 106 | buildConfigurationList = 4BED02EE205A26070050BACF /* Build configuration list for PBXProject "DebuggableContext" */; 107 | compatibilityVersion = "Xcode 8.0"; 108 | developmentRegion = en; 109 | hasScannedForEncodings = 0; 110 | knownRegions = ( 111 | en, 112 | ); 113 | mainGroup = 4BED02EA205A26070050BACF; 114 | productRefGroup = 4BED02F5205A26070050BACF /* Products */; 115 | projectDirPath = ""; 116 | projectRoot = ""; 117 | targets = ( 118 | 4BED02F3205A26070050BACF /* DebuggableContext */, 119 | ); 120 | }; 121 | /* End PBXProject section */ 122 | 123 | /* Begin PBXResourcesBuildPhase section */ 124 | 4BED02F2205A26070050BACF /* Resources */ = { 125 | isa = PBXResourcesBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXResourcesBuildPhase section */ 132 | 133 | /* Begin PBXSourcesBuildPhase section */ 134 | 4BED02EF205A26070050BACF /* Sources */ = { 135 | isa = PBXSourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | 4BED033C205A27330050BACF /* DebuggableContext.swift in Sources */, 139 | ); 140 | runOnlyForDeploymentPostprocessing = 0; 141 | }; 142 | /* End PBXSourcesBuildPhase section */ 143 | 144 | /* Begin XCBuildConfiguration section */ 145 | 4BED02FA205A26070050BACF /* Debug */ = { 146 | isa = XCBuildConfiguration; 147 | buildSettings = { 148 | ALWAYS_SEARCH_USER_PATHS = NO; 149 | CLANG_ANALYZER_NONNULL = YES; 150 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 151 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 152 | CLANG_CXX_LIBRARY = "libc++"; 153 | CLANG_ENABLE_MODULES = YES; 154 | CLANG_ENABLE_OBJC_ARC = YES; 155 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 156 | CLANG_WARN_BOOL_CONVERSION = YES; 157 | CLANG_WARN_COMMA = YES; 158 | CLANG_WARN_CONSTANT_CONVERSION = YES; 159 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 160 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 161 | CLANG_WARN_EMPTY_BODY = YES; 162 | CLANG_WARN_ENUM_CONVERSION = YES; 163 | CLANG_WARN_INFINITE_RECURSION = YES; 164 | CLANG_WARN_INT_CONVERSION = YES; 165 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 166 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 167 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 168 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 169 | CLANG_WARN_STRICT_PROTOTYPES = YES; 170 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 171 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 172 | CLANG_WARN_UNREACHABLE_CODE = YES; 173 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 174 | CODE_SIGN_IDENTITY = "iPhone Developer"; 175 | COPY_PHASE_STRIP = NO; 176 | CURRENT_PROJECT_VERSION = 1; 177 | DEBUG_INFORMATION_FORMAT = dwarf; 178 | ENABLE_STRICT_OBJC_MSGSEND = YES; 179 | ENABLE_TESTABILITY = YES; 180 | GCC_C_LANGUAGE_STANDARD = gnu11; 181 | GCC_DYNAMIC_NO_PIC = NO; 182 | GCC_NO_COMMON_BLOCKS = YES; 183 | GCC_OPTIMIZATION_LEVEL = 0; 184 | GCC_PREPROCESSOR_DEFINITIONS = ( 185 | "DEBUG=1", 186 | "$(inherited)", 187 | ); 188 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 189 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 190 | GCC_WARN_UNDECLARED_SELECTOR = YES; 191 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 192 | GCC_WARN_UNUSED_FUNCTION = YES; 193 | GCC_WARN_UNUSED_VARIABLE = YES; 194 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 195 | MTL_ENABLE_DEBUG_INFO = YES; 196 | ONLY_ACTIVE_ARCH = YES; 197 | SDKROOT = iphoneos; 198 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 199 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 200 | VERSIONING_SYSTEM = "apple-generic"; 201 | VERSION_INFO_PREFIX = ""; 202 | }; 203 | name = Debug; 204 | }; 205 | 4BED02FB205A26070050BACF /* Release */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_ANALYZER_NONNULL = YES; 210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 212 | CLANG_CXX_LIBRARY = "libc++"; 213 | CLANG_ENABLE_MODULES = YES; 214 | CLANG_ENABLE_OBJC_ARC = YES; 215 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 216 | CLANG_WARN_BOOL_CONVERSION = YES; 217 | CLANG_WARN_COMMA = YES; 218 | CLANG_WARN_CONSTANT_CONVERSION = YES; 219 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 220 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 221 | CLANG_WARN_EMPTY_BODY = YES; 222 | CLANG_WARN_ENUM_CONVERSION = YES; 223 | CLANG_WARN_INFINITE_RECURSION = YES; 224 | CLANG_WARN_INT_CONVERSION = YES; 225 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 226 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 228 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 229 | CLANG_WARN_STRICT_PROTOTYPES = YES; 230 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 231 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 232 | CLANG_WARN_UNREACHABLE_CODE = YES; 233 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 234 | CODE_SIGN_IDENTITY = "iPhone Developer"; 235 | COPY_PHASE_STRIP = NO; 236 | CURRENT_PROJECT_VERSION = 1; 237 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 238 | ENABLE_NS_ASSERTIONS = NO; 239 | ENABLE_STRICT_OBJC_MSGSEND = YES; 240 | GCC_C_LANGUAGE_STANDARD = gnu11; 241 | GCC_NO_COMMON_BLOCKS = YES; 242 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 243 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 244 | GCC_WARN_UNDECLARED_SELECTOR = YES; 245 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 246 | GCC_WARN_UNUSED_FUNCTION = YES; 247 | GCC_WARN_UNUSED_VARIABLE = YES; 248 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 249 | MTL_ENABLE_DEBUG_INFO = NO; 250 | SDKROOT = iphoneos; 251 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 252 | VALIDATE_PRODUCT = YES; 253 | VERSIONING_SYSTEM = "apple-generic"; 254 | VERSION_INFO_PREFIX = ""; 255 | }; 256 | name = Release; 257 | }; 258 | 4BED02FD205A26070050BACF /* Debug */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | CLANG_ENABLE_MODULES = YES; 262 | CODE_SIGN_IDENTITY = ""; 263 | CODE_SIGN_STYLE = Automatic; 264 | DEFINES_MODULE = YES; 265 | DEVELOPMENT_TEAM = T499X543T7; 266 | DYLIB_COMPATIBILITY_VERSION = 1; 267 | DYLIB_CURRENT_VERSION = 1; 268 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 269 | INFOPLIST_FILE = DebuggableContext/Info.plist; 270 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 271 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 272 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 273 | PRODUCT_BUNDLE_IDENTIFIER = com.onevcat.DebuggableContext; 274 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 275 | SKIP_INSTALL = YES; 276 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 277 | SWIFT_VERSION = 4.0; 278 | TARGETED_DEVICE_FAMILY = "1,2"; 279 | }; 280 | name = Debug; 281 | }; 282 | 4BED02FE205A26070050BACF /* Release */ = { 283 | isa = XCBuildConfiguration; 284 | buildSettings = { 285 | CLANG_ENABLE_MODULES = YES; 286 | CODE_SIGN_IDENTITY = ""; 287 | CODE_SIGN_STYLE = Automatic; 288 | DEFINES_MODULE = YES; 289 | DEVELOPMENT_TEAM = T499X543T7; 290 | DYLIB_COMPATIBILITY_VERSION = 1; 291 | DYLIB_CURRENT_VERSION = 1; 292 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 293 | INFOPLIST_FILE = DebuggableContext/Info.plist; 294 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 295 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 296 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 297 | PRODUCT_BUNDLE_IDENTIFIER = com.onevcat.DebuggableContext; 298 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 299 | SKIP_INSTALL = YES; 300 | SWIFT_VERSION = 4.0; 301 | TARGETED_DEVICE_FAMILY = "1,2"; 302 | }; 303 | name = Release; 304 | }; 305 | /* End XCBuildConfiguration section */ 306 | 307 | /* Begin XCConfigurationList section */ 308 | 4BED02EE205A26070050BACF /* Build configuration list for PBXProject "DebuggableContext" */ = { 309 | isa = XCConfigurationList; 310 | buildConfigurations = ( 311 | 4BED02FA205A26070050BACF /* Debug */, 312 | 4BED02FB205A26070050BACF /* Release */, 313 | ); 314 | defaultConfigurationIsVisible = 0; 315 | defaultConfigurationName = Release; 316 | }; 317 | 4BED02FC205A26070050BACF /* Build configuration list for PBXNativeTarget "DebuggableContext" */ = { 318 | isa = XCConfigurationList; 319 | buildConfigurations = ( 320 | 4BED02FD205A26070050BACF /* Debug */, 321 | 4BED02FE205A26070050BACF /* Release */, 322 | ); 323 | defaultConfigurationIsVisible = 0; 324 | defaultConfigurationName = Release; 325 | }; 326 | /* End XCConfigurationList section */ 327 | }; 328 | rootObject = 4BED02EB205A26070050BACF /* Project object */; 329 | } 330 | -------------------------------------------------------------------------------- /DebuggableContext/DebuggableContext/DebuggableContext.h: -------------------------------------------------------------------------------- 1 | // 2 | // DebuggableContext.h 3 | // DebuggableContext 4 | // 5 | // Created by Wang Wei on 2018/3/15. 6 | // Copyright © 2018年 OneV's Den. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for DebuggableContext. 12 | FOUNDATION_EXPORT double DebuggableContextVersionNumber; 13 | 14 | //! Project version string for DebuggableContext. 15 | FOUNDATION_EXPORT const unsigned char DebuggableContextVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /DebuggableContext/DebuggableContext/DebuggableContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebuggableContext.swift 3 | // DebuggableContext 4 | // 5 | // Created by Wei Wang (@onevcat) on 2018/3/15. 6 | // 7 | // MIT License 8 | // Copyright (c) 2018 Wei Wang 9 | // 10 | // Permission is hereby granted, free of charge, to any person obtaining a copy 11 | // of this software and associated documentation files (the "Software"), to deal 12 | // in the Software without restriction, including without limitation the rights 13 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the Software is 15 | // furnished to do so, subject to the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be included in all 18 | // copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | // SOFTWARE. 27 | 28 | #if DEBUG 29 | import UIKit 30 | public protocol DebuggableContext: class { 31 | /// The menu related to this debuggable context. These items will be presented in an alert sheet from the most top view controller. 32 | var debugMenus: [DebuggableContextItem] { get } 33 | 34 | /// Register `self` to debuggable list. After registered, this debuggable context will be displayed when you shake your device. 35 | func registerDebug() 36 | 37 | /// Unregister `self` from debuggable list. It is the reverting of `registerDebug`. Generally, there is no need to unregister. The 38 | /// registered items will be unregistered automatically when dealloc. 39 | func unregisterDebug() 40 | 41 | /// Context name displayed in menu. By default it is the name of self class. 42 | var debugContextName: String { get } 43 | } 44 | 45 | extension DebuggableContext { 46 | public func registerDebug() { 47 | ContextDebugResponser.shared.register(self) 48 | } 49 | 50 | public func unregisterDebug() { 51 | ContextDebugResponser.shared.unregister(self) 52 | } 53 | 54 | public var debugContextName: String { 55 | return String(describing: Self.self) 56 | } 57 | } 58 | 59 | /// Filtering which target should be displayed in the context debug menu. It is useful when you have a lot of debuggable context items, 60 | /// but only want to show one or some of them from current view controller. Make your view controller to extend this protocol and return 61 | /// `false` to exclude the `target` context. 62 | public protocol DebuggableContextFiltering { 63 | func filterContext(target: DebuggableContext) -> Bool 64 | } 65 | 66 | /// Debug button item which will be shown in the context menu. 67 | public struct DebuggableContextItem { 68 | let name: String 69 | let action: () -> Void 70 | public init(name: String, action: @escaping () -> Void) { 71 | self.name = name 72 | self.action = action 73 | } 74 | } 75 | 76 | final public class ShakeDetectingWindow: UIWindow { 77 | override public func motionEnded(_ motion: UIEventSubtype, with event: UIEvent?) { 78 | if motion == .motionShake { 79 | NotificationCenter.default.post(name: .contextDebugDeviceShakenNotification, object: self) 80 | return 81 | } 82 | 83 | super.motionEnded(motion, with: event) 84 | } 85 | } 86 | 87 | // Private 88 | private let logTag = "[ContextDebuggable]" 89 | private extension Notification.Name { 90 | static let contextDebugDeviceShakenNotification = NSNotification.Name("com.onevcat.ContextDebugDeviceShakenNotification") 91 | } 92 | 93 | private final class ContextDebugResponser { 94 | static let shared = ContextDebugResponser() 95 | private let table = NSMapTable(valueOptions: .weakMemory) 96 | private init() { 97 | NotificationCenter.default.addObserver(self, selector: #selector(onShaken), 98 | name: .contextDebugDeviceShakenNotification, object: nil) 99 | } 100 | 101 | func register(_ target: DebuggableContext) { 102 | var key = target.debugContextName as NSString 103 | if table.object(forKey: key) != nil { 104 | var t = target 105 | withUnsafePointer(to: &t) { key = "\(key)-\($0)" as NSString } 106 | } 107 | table.setObject(target, forKey: key) 108 | } 109 | 110 | func unregister(_ target: DebuggableContext) { 111 | let allKeys = table.keyEnumerator().allObjects 112 | for key in allKeys { 113 | guard let k = key as? NSString else { continue } 114 | if let value = table.object(forKey: k), value === target { 115 | table.removeObject(forKey: k) 116 | break 117 | } 118 | } 119 | } 120 | 121 | @objc private func onShaken() { 122 | 123 | guard let allContext = (table.objectEnumerator()?.allObjects.map { $0 as! DebuggableContext }) else { 124 | assertionFailure("\(logTag) Corrupted context table.") 125 | return 126 | } 127 | 128 | guard let topVC = UIApplication.shared.keyWindow?.rootViewController?.topMostViewController() else { 129 | assertionFailure("\(logTag) Fails to show debug menu. Cannot get most top view controller.") 130 | return 131 | } 132 | 133 | let filter = (topVC as? DebuggableContextFiltering)?.filterContext ?? { _ in true } 134 | let validContext = allContext.filter(filter) 135 | 136 | switch validContext.count { 137 | case 0: print("\(logTag) No listening debugable object. Skipping...") 138 | case 1: show(context: validContext[0], in: topVC) 139 | default: show(contexts: validContext, in: topVC) 140 | } 141 | } 142 | 143 | private func show(contexts: [DebuggableContext], in viewController: UIViewController) { 144 | 145 | print("\(logTag) Presenting debuggable context menu from \(viewController)") 146 | 147 | let alert = UIAlertController(title: "Valid Context", message: nil, preferredStyle: .actionSheet) 148 | let actions = contexts.map { context in 149 | return UIAlertAction(context) { self.show(context: context, in: viewController) } 150 | } + [.cancel] 151 | actions.forEach(alert.addAction) 152 | 153 | viewController.present(alert, animated: true) 154 | } 155 | 156 | private func show(context: DebuggableContext, in viewController: UIViewController) { 157 | 158 | print("\(logTag) Presenting debuggable context menu from \(viewController)") 159 | 160 | let alert = UIAlertController(title: context.debugContextName, message: nil, preferredStyle: .actionSheet) 161 | let actions = context.debugMenus.map(UIAlertAction.init) + [.cancel] 162 | actions.forEach(alert.addAction) 163 | 164 | viewController.present(alert, animated: true) 165 | } 166 | } 167 | 168 | private extension UIAlertAction { 169 | static let cancel = UIAlertAction(title: "Cancel", style: .cancel) 170 | 171 | convenience init(_ context: DebuggableContext, done: @escaping () -> Void) { 172 | self.init(title: context.debugContextName, style: .default) { _ in done() } 173 | } 174 | 175 | convenience init(_ item: DebuggableContextItem) { 176 | self.init(title: item.name, style: .default) { _ in item.action() } 177 | } 178 | } 179 | 180 | private extension UIViewController { 181 | @objc func topMostViewController() -> UIViewController { 182 | if let presentedViewController = presentedViewController { 183 | return presentedViewController.topMostViewController() 184 | } else { 185 | let childViewController = view.subviews.first { $0.next is UIViewController } 186 | .map { $0.next as! UIViewController } 187 | return childViewController?.topMostViewController() ?? self 188 | } 189 | } 190 | } 191 | 192 | private extension UITabBarController { 193 | override func topMostViewController() -> UIViewController { 194 | return selectedViewController!.topMostViewController() 195 | } 196 | } 197 | 198 | private extension UINavigationController { 199 | override func topMostViewController() -> UIViewController { 200 | return visibleViewController!.topMostViewController() 201 | } 202 | } 203 | 204 | private extension UIWindow { 205 | func topViewController() -> UIViewController? { 206 | return rootViewController?.topMostViewController() 207 | } 208 | } 209 | #endif 210 | -------------------------------------------------------------------------------- /DebuggableContext/DebuggableContext/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DebuggableContextDemo/DebuggableContextDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 4BED032A205A26F70050BACF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BED0329205A26F70050BACF /* AppDelegate.swift */; }; 11 | 4BED032C205A26F70050BACF /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BED032B205A26F70050BACF /* ViewController.swift */; }; 12 | 4BED032F205A26F70050BACF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BED032D205A26F70050BACF /* Main.storyboard */; }; 13 | 4BED0331205A26F70050BACF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4BED0330205A26F70050BACF /* Assets.xcassets */; }; 14 | 4BED0334205A26F70050BACF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BED0332205A26F70050BACF /* LaunchScreen.storyboard */; }; 15 | 4BED0341205A3EC60050BACF /* DebuggableContext.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BED0342205A3EC60050BACF /* DebuggableContext.framework */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 4BED0326205A26F70050BACF /* DebuggableContextDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DebuggableContextDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 4BED0329205A26F70050BACF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 4BED032B205A26F70050BACF /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 4BED032E205A26F70050BACF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 4BED0330205A26F70050BACF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 24 | 4BED0333205A26F70050BACF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 25 | 4BED0335205A26F70050BACF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 26 | 4BED0342205A3EC60050BACF /* DebuggableContext.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = DebuggableContext.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 4BED0323205A26F70050BACF /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | 4BED0341205A3EC60050BACF /* DebuggableContext.framework in Frameworks */, 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 4BED031D205A26F70050BACF = { 42 | isa = PBXGroup; 43 | children = ( 44 | 4BED0328205A26F70050BACF /* DebuggableContextDemo */, 45 | 4BED0327205A26F70050BACF /* Products */, 46 | 4BED0340205A3EC60050BACF /* Frameworks */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | 4BED0327205A26F70050BACF /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | 4BED0326205A26F70050BACF /* DebuggableContextDemo.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | 4BED0328205A26F70050BACF /* DebuggableContextDemo */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 4BED0329205A26F70050BACF /* AppDelegate.swift */, 62 | 4BED032B205A26F70050BACF /* ViewController.swift */, 63 | 4BED032D205A26F70050BACF /* Main.storyboard */, 64 | 4BED0330205A26F70050BACF /* Assets.xcassets */, 65 | 4BED0332205A26F70050BACF /* LaunchScreen.storyboard */, 66 | 4BED0335205A26F70050BACF /* Info.plist */, 67 | ); 68 | path = DebuggableContextDemo; 69 | sourceTree = ""; 70 | }; 71 | 4BED0340205A3EC60050BACF /* Frameworks */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 4BED0342205A3EC60050BACF /* DebuggableContext.framework */, 75 | ); 76 | name = Frameworks; 77 | sourceTree = ""; 78 | }; 79 | /* End PBXGroup section */ 80 | 81 | /* Begin PBXNativeTarget section */ 82 | 4BED0325205A26F70050BACF /* DebuggableContextDemo */ = { 83 | isa = PBXNativeTarget; 84 | buildConfigurationList = 4BED0338205A26F70050BACF /* Build configuration list for PBXNativeTarget "DebuggableContextDemo" */; 85 | buildPhases = ( 86 | 4BED0322205A26F70050BACF /* Sources */, 87 | 4BED0323205A26F70050BACF /* Frameworks */, 88 | 4BED0324205A26F70050BACF /* Resources */, 89 | ); 90 | buildRules = ( 91 | ); 92 | dependencies = ( 93 | ); 94 | name = DebuggableContextDemo; 95 | productName = DebuggableContextDemo; 96 | productReference = 4BED0326205A26F70050BACF /* DebuggableContextDemo.app */; 97 | productType = "com.apple.product-type.application"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | 4BED031E205A26F70050BACF /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | LastSwiftUpdateCheck = 0920; 106 | LastUpgradeCheck = 0920; 107 | ORGANIZATIONNAME = "OneV's Den"; 108 | TargetAttributes = { 109 | 4BED0325205A26F70050BACF = { 110 | CreatedOnToolsVersion = 9.2; 111 | ProvisioningStyle = Automatic; 112 | }; 113 | }; 114 | }; 115 | buildConfigurationList = 4BED0321205A26F70050BACF /* Build configuration list for PBXProject "DebuggableContextDemo" */; 116 | compatibilityVersion = "Xcode 8.0"; 117 | developmentRegion = en; 118 | hasScannedForEncodings = 0; 119 | knownRegions = ( 120 | en, 121 | Base, 122 | ); 123 | mainGroup = 4BED031D205A26F70050BACF; 124 | productRefGroup = 4BED0327205A26F70050BACF /* Products */; 125 | projectDirPath = ""; 126 | projectRoot = ""; 127 | targets = ( 128 | 4BED0325205A26F70050BACF /* DebuggableContextDemo */, 129 | ); 130 | }; 131 | /* End PBXProject section */ 132 | 133 | /* Begin PBXResourcesBuildPhase section */ 134 | 4BED0324205A26F70050BACF /* Resources */ = { 135 | isa = PBXResourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | 4BED0334205A26F70050BACF /* LaunchScreen.storyboard in Resources */, 139 | 4BED0331205A26F70050BACF /* Assets.xcassets in Resources */, 140 | 4BED032F205A26F70050BACF /* Main.storyboard in Resources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXResourcesBuildPhase section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | 4BED0322205A26F70050BACF /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | 4BED032C205A26F70050BACF /* ViewController.swift in Sources */, 152 | 4BED032A205A26F70050BACF /* AppDelegate.swift in Sources */, 153 | ); 154 | runOnlyForDeploymentPostprocessing = 0; 155 | }; 156 | /* End PBXSourcesBuildPhase section */ 157 | 158 | /* Begin PBXVariantGroup section */ 159 | 4BED032D205A26F70050BACF /* Main.storyboard */ = { 160 | isa = PBXVariantGroup; 161 | children = ( 162 | 4BED032E205A26F70050BACF /* Base */, 163 | ); 164 | name = Main.storyboard; 165 | sourceTree = ""; 166 | }; 167 | 4BED0332205A26F70050BACF /* LaunchScreen.storyboard */ = { 168 | isa = PBXVariantGroup; 169 | children = ( 170 | 4BED0333205A26F70050BACF /* Base */, 171 | ); 172 | name = LaunchScreen.storyboard; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXVariantGroup section */ 176 | 177 | /* Begin XCBuildConfiguration section */ 178 | 4BED0336205A26F70050BACF /* Debug */ = { 179 | isa = XCBuildConfiguration; 180 | buildSettings = { 181 | ALWAYS_SEARCH_USER_PATHS = NO; 182 | CLANG_ANALYZER_NONNULL = YES; 183 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 184 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 185 | CLANG_CXX_LIBRARY = "libc++"; 186 | CLANG_ENABLE_MODULES = YES; 187 | CLANG_ENABLE_OBJC_ARC = YES; 188 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 189 | CLANG_WARN_BOOL_CONVERSION = YES; 190 | CLANG_WARN_COMMA = YES; 191 | CLANG_WARN_CONSTANT_CONVERSION = YES; 192 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 193 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 194 | CLANG_WARN_EMPTY_BODY = YES; 195 | CLANG_WARN_ENUM_CONVERSION = YES; 196 | CLANG_WARN_INFINITE_RECURSION = YES; 197 | CLANG_WARN_INT_CONVERSION = YES; 198 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 199 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 200 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 201 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 202 | CLANG_WARN_STRICT_PROTOTYPES = YES; 203 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 204 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 205 | CLANG_WARN_UNREACHABLE_CODE = YES; 206 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 207 | CODE_SIGN_IDENTITY = "iPhone Developer"; 208 | COPY_PHASE_STRIP = NO; 209 | DEBUG_INFORMATION_FORMAT = dwarf; 210 | ENABLE_STRICT_OBJC_MSGSEND = YES; 211 | ENABLE_TESTABILITY = YES; 212 | GCC_C_LANGUAGE_STANDARD = gnu11; 213 | GCC_DYNAMIC_NO_PIC = NO; 214 | GCC_NO_COMMON_BLOCKS = YES; 215 | GCC_OPTIMIZATION_LEVEL = 0; 216 | GCC_PREPROCESSOR_DEFINITIONS = ( 217 | "DEBUG=1", 218 | "$(inherited)", 219 | ); 220 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 221 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 222 | GCC_WARN_UNDECLARED_SELECTOR = YES; 223 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 224 | GCC_WARN_UNUSED_FUNCTION = YES; 225 | GCC_WARN_UNUSED_VARIABLE = YES; 226 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 227 | MTL_ENABLE_DEBUG_INFO = YES; 228 | ONLY_ACTIVE_ARCH = YES; 229 | SDKROOT = iphoneos; 230 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 231 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 232 | }; 233 | name = Debug; 234 | }; 235 | 4BED0337205A26F70050BACF /* Release */ = { 236 | isa = XCBuildConfiguration; 237 | buildSettings = { 238 | ALWAYS_SEARCH_USER_PATHS = NO; 239 | CLANG_ANALYZER_NONNULL = YES; 240 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_MODULES = YES; 244 | CLANG_ENABLE_OBJC_ARC = YES; 245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 246 | CLANG_WARN_BOOL_CONVERSION = YES; 247 | CLANG_WARN_COMMA = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 251 | CLANG_WARN_EMPTY_BODY = YES; 252 | CLANG_WARN_ENUM_CONVERSION = YES; 253 | CLANG_WARN_INFINITE_RECURSION = YES; 254 | CLANG_WARN_INT_CONVERSION = YES; 255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 258 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 259 | CLANG_WARN_STRICT_PROTOTYPES = YES; 260 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 261 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 262 | CLANG_WARN_UNREACHABLE_CODE = YES; 263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 264 | CODE_SIGN_IDENTITY = "iPhone Developer"; 265 | COPY_PHASE_STRIP = NO; 266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 267 | ENABLE_NS_ASSERTIONS = NO; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu11; 270 | GCC_NO_COMMON_BLOCKS = YES; 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 273 | GCC_WARN_UNDECLARED_SELECTOR = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 275 | GCC_WARN_UNUSED_FUNCTION = YES; 276 | GCC_WARN_UNUSED_VARIABLE = YES; 277 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 278 | MTL_ENABLE_DEBUG_INFO = NO; 279 | SDKROOT = iphoneos; 280 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 281 | VALIDATE_PRODUCT = YES; 282 | }; 283 | name = Release; 284 | }; 285 | 4BED0339205A26F70050BACF /* Debug */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CODE_SIGN_STYLE = Automatic; 290 | DEVELOPMENT_TEAM = T499X543T7; 291 | INFOPLIST_FILE = DebuggableContextDemo/Info.plist; 292 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 293 | PRODUCT_BUNDLE_IDENTIFIER = com.onevcat.DebuggableContextDemo; 294 | PRODUCT_NAME = "$(TARGET_NAME)"; 295 | SWIFT_VERSION = 4.0; 296 | TARGETED_DEVICE_FAMILY = "1,2"; 297 | }; 298 | name = Debug; 299 | }; 300 | 4BED033A205A26F70050BACF /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 304 | CODE_SIGN_STYLE = Automatic; 305 | DEVELOPMENT_TEAM = T499X543T7; 306 | INFOPLIST_FILE = DebuggableContextDemo/Info.plist; 307 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 308 | PRODUCT_BUNDLE_IDENTIFIER = com.onevcat.DebuggableContextDemo; 309 | PRODUCT_NAME = "$(TARGET_NAME)"; 310 | SWIFT_VERSION = 4.0; 311 | TARGETED_DEVICE_FAMILY = "1,2"; 312 | }; 313 | name = Release; 314 | }; 315 | /* End XCBuildConfiguration section */ 316 | 317 | /* Begin XCConfigurationList section */ 318 | 4BED0321205A26F70050BACF /* Build configuration list for PBXProject "DebuggableContextDemo" */ = { 319 | isa = XCConfigurationList; 320 | buildConfigurations = ( 321 | 4BED0336205A26F70050BACF /* Debug */, 322 | 4BED0337205A26F70050BACF /* Release */, 323 | ); 324 | defaultConfigurationIsVisible = 0; 325 | defaultConfigurationName = Release; 326 | }; 327 | 4BED0338205A26F70050BACF /* Build configuration list for PBXNativeTarget "DebuggableContextDemo" */ = { 328 | isa = XCConfigurationList; 329 | buildConfigurations = ( 330 | 4BED0339205A26F70050BACF /* Debug */, 331 | 4BED033A205A26F70050BACF /* Release */, 332 | ); 333 | defaultConfigurationIsVisible = 0; 334 | defaultConfigurationName = Release; 335 | }; 336 | /* End XCConfigurationList section */ 337 | }; 338 | rootObject = 4BED031E205A26F70050BACF /* Project object */; 339 | } 340 | -------------------------------------------------------------------------------- /DebuggableContextDemo/DebuggableContextDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DebuggableContextDemo 4 | // 5 | // Created by Wang Wei on 2018/3/15. 6 | // Copyright © 2018年 OneV's Den. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DebuggableContext 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | #if DEBUG 16 | var _shakeDetectingWindow: ShakeDetectingWindow? 17 | var window: UIWindow? { 18 | get { 19 | return _shakeDetectingWindow ?? { 20 | _shakeDetectingWindow = ShakeDetectingWindow(frame: UIScreen.main.bounds) 21 | return _shakeDetectingWindow 22 | }() 23 | } 24 | set {} 25 | } 26 | #else 27 | var window: UIWindow? 28 | #endif 29 | 30 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 31 | // Override point for customization after application launch. 32 | return true 33 | } 34 | 35 | func applicationWillResignActive(_ application: UIApplication) { 36 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 37 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 38 | } 39 | 40 | func applicationDidEnterBackground(_ application: UIApplication) { 41 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 42 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 43 | } 44 | 45 | func applicationWillEnterForeground(_ application: UIApplication) { 46 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 47 | } 48 | 49 | func applicationDidBecomeActive(_ application: UIApplication) { 50 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 51 | } 52 | 53 | func applicationWillTerminate(_ application: UIApplication) { 54 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 55 | } 56 | 57 | 58 | } 59 | 60 | -------------------------------------------------------------------------------- /DebuggableContextDemo/DebuggableContextDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /DebuggableContextDemo/DebuggableContextDemo/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /DebuggableContextDemo/DebuggableContextDemo/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 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 63 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /DebuggableContextDemo/DebuggableContextDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /DebuggableContextDemo/DebuggableContextDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DebuggableContextDemo 4 | // 5 | // Created by Wang Wei on 2018/3/15. 6 | // Copyright © 2018年 OneV's Den. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import DebuggableContext 11 | 12 | class ViewController: UIViewController { 13 | @IBAction func dismiss(segue: UIStoryboardSegue) {} 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | #if DEBUG 17 | registerDebug() 18 | #endif 19 | } 20 | } 21 | 22 | #if DEBUG 23 | extension ViewController: DebuggableContext { 24 | var debugMenus: [DebuggableContextItem] { 25 | return [ 26 | .init(name: "Color To Cupid") { [weak self] in 27 | self?.view.backgroundColor = UIColor(red:0.94, green:0.73, blue:0.83, alpha:1.00) 28 | }, 29 | .init(name: "Color To Mint") { [weak self] in 30 | self?.view.backgroundColor = UIColor(red:0.71, green:0.96, blue:0.82, alpha:1.00) 31 | } 32 | ] 33 | } 34 | } 35 | #endif 36 | 37 | class AnotherViewController: UIViewController { 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | #if DEBUG 41 | registerDebug() 42 | #endif 43 | } 44 | } 45 | 46 | #if DEBUG 47 | extension AnotherViewController: DebuggableContext { 48 | var debugMenus: [DebuggableContextItem] { 49 | return [ .init(name: "Say Hello") { print("Hello World! from \(self)") } ] 50 | } 51 | } 52 | 53 | extension AnotherViewController: DebuggableContextFiltering { 54 | func filterContext(target: DebuggableContext) -> Bool { 55 | return target === self 56 | } 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Wei Wang 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 | # DebuggableContext 2 | 3 | DebuggableContext provides an easy to show an action sheet for debugging purpose when shaking your iOS device or simulator. 4 | 5 | ## Usage 6 | 7 | 1. Make your class (usually a view controller) to extend `DebuggableContext`. It defines the items which should be displayed in debug menu: 8 | 9 | ```swift 10 | #if DEBUG 11 | extension ViewController: DebuggableContext { 12 | var debugMenus: [DebuggableContextItem] { 13 | return [ 14 | .init(name: "Color To Cupid") { [weak self] in 15 | self?.view.backgroundColor = UIColor(red:0.94, green:0.73, blue:0.83, alpha:1.00) 16 | }, 17 | .init(name: "Color To Mint") { [weak self] in 18 | self?.view.backgroundColor = UIColor(red:0.71, green:0.96, blue:0.82, alpha:1.00) 19 | } 20 | ] 21 | } 22 | } 23 | 24 | extension AnotherViewController: DebuggableContext { 25 | var debugMenus: [DebuggableContextItem] { 26 | return [ .init(name: "Say Hello") { print("Hello World!") } ] 27 | } 28 | } 29 | #endif 30 | ``` 31 | 32 | 2. Call `registerDebug` at proper time. For view controllers, `viewDidLoad` would be a good choice: 33 | 34 | ```swift 35 | // In both ViewController and AnotherViewController 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | #if DEBUG 39 | registerDebug() 40 | #endif 41 | } 42 | ``` 43 | 44 | 3. Replace `var window: UIWindow?` in your `AppDelegate` to: 45 | 46 | ```swift 47 | #if DEBUG 48 | var _shakeDetectingWindow: ShakeDetectingWindow? 49 | var window: UIWindow? { 50 | get { 51 | return _shakeDetectingWindow ?? { 52 | _shakeDetectingWindow = ShakeDetectingWindow(frame: UIScreen.main.bounds) 53 | return _shakeDetectingWindow 54 | }() 55 | } 56 | set {} 57 | } 58 | #else 59 | var window: UIWindow? 60 | #endif 61 | ``` 62 | 63 | 4. Run your app, and trigger a shake gesture (shake on a real device or Ctrl+Cmd+Z on simulator), a debug action sheet will be presented from the top most view controller: 64 | 65 | ![](https://raw.githubusercontent.com/onevcat/DebuggableContext/master/RepoAssets/demo.gif) 66 | 67 | 68 | ## Installation 69 | 70 | No plan for CocoaPods or Carthage support currently (as it is still tiny). 71 | 72 | Just grab [DebuggableContext.swift](https://github.com/onevcat/DebuggableContext/blob/master/DebuggableContext/DebuggableContext/DebuggableContext.swift) and throw it to your project, then you could begin to play with it. 73 | 74 | ## FAQ 75 | 76 | #### Any overhead? 77 | 78 | No. Everything including `DebuggableContext` itself is wrapped with `#if DEBUG` compilation condition. So as long as you set the in the `SWIFT_ACTIVE_COMPILATION_CONDITIONS` only for your Debug configuration (by default it is set automatically when you create a project in Xcode), it will not even contained in your release build. 79 | 80 | ![](https://raw.githubusercontent.com/onevcat/DebuggableContext/master/RepoAssets/compile-flag.png) 81 | 82 | 83 | #### It does not compile in Release mode. 84 | 85 | Check you have wrapped your debuggable context related code in `#if DEBUG`. 86 | 87 | #### It's too noisy when I have added too much context. 88 | 89 | Make your presenting view controller to extend `DebuggableContextFiltering`, and filter the ones you need. An example to only allow context menu defined as `self`: 90 | 91 | ```swift 92 | extension AnotherViewController: DebuggableContextFiltering { 93 | func filterContext(target: DebuggableContext) -> Bool { 94 | return target === self 95 | } 96 | } 97 | ``` 98 | 99 | There is also a console log before an action sheet being displayed, to tell you which view controller is the presenting view controller. 100 | 101 | #### Do I need to call `unregisterDebug()` myself? 102 | 103 | No. The registered context will be removed automatically when it gets `deinit`ed. However, you are free to call it whenever you want, to remove it from the next shake gesture. 104 | 105 | -------------------------------------------------------------------------------- /RepoAssets/compile-flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/DebuggableContext/b418db38a4d58750846ed1d190da2a71f43c7abd/RepoAssets/compile-flag.png -------------------------------------------------------------------------------- /RepoAssets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onevcat/DebuggableContext/b418db38a4d58750846ed1d190da2a71f43c7abd/RepoAssets/demo.gif --------------------------------------------------------------------------------