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