├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── Tracer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── Tracer.xcscheme ├── Tracer ├── .DS_Store ├── Info.plist ├── Private │ ├── NSArray+Tracer.h │ ├── NSArray+Tracer.m │ ├── NSDate+Tracer.h │ ├── NSDate+Tracer.m │ ├── NSDictionary+Tracer.h │ ├── NSDictionary+Tracer.m │ ├── TRCAspects.h │ ├── TRCAspects.m │ ├── TRCCall+Private.h │ ├── TRCDispatchFunctions.h │ ├── TRCDispatchFunctions.m │ ├── TRCErrors+Private.h │ ├── TRCNotNil.h │ ├── TRCTrace+Private.h │ └── TRCValue+Private.h ├── TRCBlocks.h ├── TRCCall.h ├── TRCCall.m ├── TRCErrors.h ├── TRCErrors.m ├── TRCFixtureProvider.h ├── TRCJsonDecodable.h ├── TRCJsonEncodable.h ├── TRCPlayer.h ├── TRCPlayer.m ├── TRCRecorder.h ├── TRCRecorder.m ├── TRCTrace.h ├── TRCTrace.m ├── TRCType.h ├── TRCValue.h ├── TRCValue.m └── Tracer.h ├── TracerTests ├── .DS_Store ├── ErrorTests.m ├── FixtureProviderTests.m ├── Info.plist ├── LoadFromFileTests.m ├── NSArray+TracerTests.m ├── NSDictionary+TracerTests.m ├── RetValueTests.m ├── RetVoidMultiArgTests.m ├── RetVoidSingleArgTests.m ├── TRCTestTarget.h ├── TRCTestTarget.m ├── saved_trace.json └── trace_bt_scan_connect.json └── tracer-objc.podspec /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe/tracer-objc/03abf6d5bfb403114ac9fd80d6aa1f5b49662d49/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | # CocoaPods 32 | # 33 | # We recommend against adding the Pods directory to your .gitignore. However 34 | # you should judge for yourself, the pros and cons are mentioned at: 35 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 36 | # 37 | # Pods/ 38 | 39 | # Carthage 40 | # 41 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 42 | # Carthage/Checkouts 43 | 44 | Carthage/Build 45 | 46 | # fastlane 47 | # 48 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 49 | # screenshots whenever they are needed. 50 | # For more information about the recommended setup visit: 51 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 52 | 53 | fastlane/report.xml 54 | fastlane/Preview.html 55 | fastlane/screenshots/**/*.png 56 | fastlane/test_output 57 | 58 | # Code Injection 59 | # 60 | # After new code Injection tools there's a generated folder /iOSInjectionProject 61 | # https://github.com/johnno1962/injectionforxcode 62 | 63 | iOSInjectionProject/ 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018- Stripe, Inc. (https://stripe.com) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tracer 2 | 3 | Tracer is an experimental testing tool that lets you record & play back the behavior of arbitrary objects in Objective-C. 4 | 5 | Let's say you have a dependency in your code, `ThatThing`. You call `ThatThing`, it calls you back, and behavior varies depending on user input or environmental factors. 6 | 7 | ```objective-c 8 | @protocol ThatInterface 9 | - (void)someCommand:(int)i; 10 | - (void)someOtherCommand:(NSArray *)objects; 11 | // ... 12 | - (void)onError:(NSError *)error; 13 | - (void)onOtherError:(NSError *)error; 14 | @end 15 | 16 | @interface ThatThing : NSObject 17 | @end 18 | ``` 19 | 20 | Testing complex async behavior is hard, especially if you don't control the source of behavior. 21 | 22 | Tracer lets you **record behavior** of `ThatThing` as a trace: 23 | 24 | ```objective-c 25 | ThatThing *thing = [ThatThing new]; 26 | TRCRecorder *recorder = [TRCRecorder new]; 27 | [recorder startRecording:thing protocol:@protocol(ThatInterface)]; 28 | NSString *result = [thing someCommand:-100]; 29 | [recorder stopRecording:thing protocol:@protocol(ThatInterface) completion:^(TRCTrace *trace, NSError *error) { 30 | // save trace 31 | }]; 32 | ``` 33 | 34 | After recording completes, Tracer prints the trace to the console as JSON, so you can **save behavior to a file**. 35 | 36 | ```txt 37 | 2019-04-17 23:01:22.689124-0700 xctest[62038:4377601] -----BEGIN TRACE JSON----- 38 | { 39 | "start_ms" : 1551678464427, 40 | "id" : "trace", 41 | "protocol" : "SomeProtocol", 42 | "calls" : [ 43 | { 44 | "id" : "call", 45 | "start_ms" : 203, 46 | "method" : "someCommand:", 47 | "arguments" : [ 48 | { 49 | "id" : "value", 50 | "type" : "int", 51 | "object_type" : "not_an_object", 52 | "object_value" : -100 53 | } 54 | ], 55 | "return_value" : { 56 | "id" : "value", 57 | "type" : "void", 58 | } 59 | } 60 | ] 61 | } 62 | -----END TRACE JSON----- 63 | ``` 64 | 65 | In your tests, instead of mocking the complex behavior of `ThatThing`, you can simply **play recorded behavior**: 66 | 67 | ```objective-c 68 | ThatThing *thing = [ThatThing new]; 69 | TRCTrace *trace = [TRCTrace loadFromJSONFile:@"saved_trace"]; 70 | [TRCPlayer playTrace:trace onTarget:thing completion:^(NSError * _Nullable error) { 71 | 72 | }]; 73 | ``` 74 | 75 | ### Current limitations 76 | 77 | - Tracer doesn't support hooking async behavior (e.g. completion blocks) 78 | - You must provide a protocol to scope recording 79 | - Optional protocol methods won't be recorded 80 | - Naive introspection when recording unknown object types (Tracer simply records the object's `description`) 81 | - Playback is always on the main thread 82 | 83 | 84 | -------------------------------------------------------------------------------- /Tracer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C106B33D22211F7B00504CF9 /* TRCJsonEncodable.h in Headers */ = {isa = PBXBuildFile; fileRef = C106B33C22211F7A00504CF9 /* TRCJsonEncodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | C106B33F2221207300504CF9 /* TRCBlocks.h in Headers */ = {isa = PBXBuildFile; fileRef = C106B33E2221207300504CF9 /* TRCBlocks.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | C106B343222126A100504CF9 /* TRCPlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = C106B341222126A100504CF9 /* TRCPlayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 13 | C106B344222126A100504CF9 /* TRCPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = C106B342222126A100504CF9 /* TRCPlayer.m */; }; 14 | C106B3482221445300504CF9 /* TRCTestTarget.m in Sources */ = {isa = PBXBuildFile; fileRef = C106B3472221445300504CF9 /* TRCTestTarget.m */; }; 15 | C106B3492221446500504CF9 /* TRCTestTarget.m in Sources */ = {isa = PBXBuildFile; fileRef = C106B3472221445300504CF9 /* TRCTestTarget.m */; }; 16 | C106B34A2221446800504CF9 /* TRCTestTarget.h in Headers */ = {isa = PBXBuildFile; fileRef = C1D7ABE5222104670040808A /* TRCTestTarget.h */; }; 17 | C106B351222144EC00504CF9 /* TRCDispatchFunctions.h in Headers */ = {isa = PBXBuildFile; fileRef = C106B34F222144EC00504CF9 /* TRCDispatchFunctions.h */; }; 18 | C106B352222144EC00504CF9 /* TRCDispatchFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = C106B350222144EC00504CF9 /* TRCDispatchFunctions.m */; }; 19 | C106B353222144F800504CF9 /* TRCDispatchFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = C106B350222144EC00504CF9 /* TRCDispatchFunctions.m */; }; 20 | C10CCD39222CDF1100F6E604 /* RetValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C10CCD38222CDF1100F6E604 /* RetValueTests.m */; }; 21 | C10CCD3B222CE08500F6E604 /* TRCType.h in Headers */ = {isa = PBXBuildFile; fileRef = C10CCD3A222CE08500F6E604 /* TRCType.h */; settings = {ATTRIBUTES = (Public, ); }; }; 22 | C10CCD3D222CE13000F6E604 /* TRCValue.h in Headers */ = {isa = PBXBuildFile; fileRef = C10CCD3C222CE13000F6E604 /* TRCValue.h */; settings = {ATTRIBUTES = (Public, ); }; }; 23 | C118071422722A790048BB83 /* trace_bt_scan_connect.json in Resources */ = {isa = PBXBuildFile; fileRef = C118071322722A790048BB83 /* trace_bt_scan_connect.json */; }; 24 | C118071622722D980048BB83 /* TRCJsonDecodable.h in Headers */ = {isa = PBXBuildFile; fileRef = C118071522722D980048BB83 /* TRCJsonDecodable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 25 | C118071822722EC30048BB83 /* NSDictionary+TracerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C118071722722EC30048BB83 /* NSDictionary+TracerTests.m */; }; 26 | C118071B22722EE60048BB83 /* NSDictionary+Tracer.h in Headers */ = {isa = PBXBuildFile; fileRef = C118071922722EE50048BB83 /* NSDictionary+Tracer.h */; }; 27 | C118071C22722EE60048BB83 /* NSDictionary+Tracer.m in Sources */ = {isa = PBXBuildFile; fileRef = C118071A22722EE60048BB83 /* NSDictionary+Tracer.m */; }; 28 | C118071F2272308C0048BB83 /* NSArray+Tracer.h in Headers */ = {isa = PBXBuildFile; fileRef = C118071D2272308C0048BB83 /* NSArray+Tracer.h */; }; 29 | C11807202272308C0048BB83 /* NSArray+Tracer.m in Sources */ = {isa = PBXBuildFile; fileRef = C118071E2272308C0048BB83 /* NSArray+Tracer.m */; }; 30 | C1180722227230940048BB83 /* NSArray+TracerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1180721227230940048BB83 /* NSArray+TracerTests.m */; }; 31 | C1570DE122683245000F3D04 /* TRCFixtureProvider.h in Headers */ = {isa = PBXBuildFile; fileRef = C1570DE022683245000F3D04 /* TRCFixtureProvider.h */; settings = {ATTRIBUTES = (Public, ); }; }; 32 | C1570DE3226837B9000F3D04 /* FixtureProviderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1570DE2226837B9000F3D04 /* FixtureProviderTests.m */; }; 33 | C1570DE522684756000F3D04 /* LoadFromFileTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1570DE422684756000F3D04 /* LoadFromFileTests.m */; }; 34 | C1570DE722684A01000F3D04 /* saved_trace.json in Resources */ = {isa = PBXBuildFile; fileRef = C1570DE622684A00000F3D04 /* saved_trace.json */; }; 35 | C184DC3C222CA06A005F7C5D /* TRCCall+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = C184DC3B222CA06A005F7C5D /* TRCCall+Private.h */; }; 36 | C1A51A9A223E18AF009C5F81 /* RetVoidMultiArgTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1A51A99223E18AF009C5F81 /* RetVoidMultiArgTests.m */; }; 37 | C1A51A9C223E1932009C5F81 /* ErrorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1A51A9B223E1932009C5F81 /* ErrorTests.m */; }; 38 | C1A51A9F223E218B009C5F81 /* TRCErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = C1A51A9D223E218B009C5F81 /* TRCErrors.h */; settings = {ATTRIBUTES = (Public, ); }; }; 39 | C1A51AA0223E218B009C5F81 /* TRCErrors.m in Sources */ = {isa = PBXBuildFile; fileRef = C1A51A9E223E218B009C5F81 /* TRCErrors.m */; }; 40 | C1B89DEA22251D3A00A9A73D /* TRCValue.m in Sources */ = {isa = PBXBuildFile; fileRef = C1B89DE822251D3A00A9A73D /* TRCValue.m */; }; 41 | C1D7ABC9222102E30040808A /* Tracer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1D7ABBF222102E30040808A /* Tracer.framework */; }; 42 | C1D7ABCE222102E30040808A /* RetVoidSingleArgTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1D7ABCD222102E30040808A /* RetVoidSingleArgTests.m */; }; 43 | C1D7ABD0222102E30040808A /* Tracer.h in Headers */ = {isa = PBXBuildFile; fileRef = C1D7ABC2222102E30040808A /* Tracer.h */; settings = {ATTRIBUTES = (Public, ); }; }; 44 | C1D7ABDB222103050040808A /* TRCRecorder.h in Headers */ = {isa = PBXBuildFile; fileRef = C1D7ABD9222103050040808A /* TRCRecorder.h */; settings = {ATTRIBUTES = (Public, ); }; }; 45 | C1D7ABDC222103050040808A /* TRCRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = C1D7ABDA222103050040808A /* TRCRecorder.m */; }; 46 | C1D7ABE3222104080040808A /* TRCAspects.m in Sources */ = {isa = PBXBuildFile; fileRef = C1D7ABE1222104080040808A /* TRCAspects.m */; }; 47 | C1D7ABE4222104080040808A /* TRCAspects.h in Headers */ = {isa = PBXBuildFile; fileRef = C1D7ABE2222104080040808A /* TRCAspects.h */; }; 48 | C1D7ABED222114300040808A /* NSDate+Tracer.h in Headers */ = {isa = PBXBuildFile; fileRef = C1D7ABEB222114300040808A /* NSDate+Tracer.h */; }; 49 | C1D7ABEE222114300040808A /* NSDate+Tracer.m in Sources */ = {isa = PBXBuildFile; fileRef = C1D7ABEC222114300040808A /* NSDate+Tracer.m */; }; 50 | C1D7ABF1222115020040808A /* TRCCall.h in Headers */ = {isa = PBXBuildFile; fileRef = C1D7ABEF222115020040808A /* TRCCall.h */; settings = {ATTRIBUTES = (Public, ); }; }; 51 | C1D7ABF2222115020040808A /* TRCCall.m in Sources */ = {isa = PBXBuildFile; fileRef = C1D7ABF0222115020040808A /* TRCCall.m */; }; 52 | C1D7ABF522211CE50040808A /* TRCTrace.h in Headers */ = {isa = PBXBuildFile; fileRef = C1D7ABF322211CE50040808A /* TRCTrace.h */; settings = {ATTRIBUTES = (Public, ); }; }; 53 | C1D7ABF622211CE50040808A /* TRCTrace.m in Sources */ = {isa = PBXBuildFile; fileRef = C1D7ABF422211CE50040808A /* TRCTrace.m */; }; 54 | /* End PBXBuildFile section */ 55 | 56 | /* Begin PBXContainerItemProxy section */ 57 | C1D7ABCA222102E30040808A /* PBXContainerItemProxy */ = { 58 | isa = PBXContainerItemProxy; 59 | containerPortal = C1D7ABB6222102E30040808A /* Project object */; 60 | proxyType = 1; 61 | remoteGlobalIDString = C1D7ABBE222102E30040808A; 62 | remoteInfo = TraceRecorder; 63 | }; 64 | /* End PBXContainerItemProxy section */ 65 | 66 | /* Begin PBXFileReference section */ 67 | C106B33C22211F7A00504CF9 /* TRCJsonEncodable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TRCJsonEncodable.h; sourceTree = ""; }; 68 | C106B33E2221207300504CF9 /* TRCBlocks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCBlocks.h; sourceTree = ""; }; 69 | C106B341222126A100504CF9 /* TRCPlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCPlayer.h; sourceTree = ""; }; 70 | C106B342222126A100504CF9 /* TRCPlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TRCPlayer.m; sourceTree = ""; }; 71 | C106B3472221445300504CF9 /* TRCTestTarget.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TRCTestTarget.m; sourceTree = ""; }; 72 | C106B34F222144EC00504CF9 /* TRCDispatchFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TRCDispatchFunctions.h; sourceTree = ""; }; 73 | C106B350222144EC00504CF9 /* TRCDispatchFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TRCDispatchFunctions.m; sourceTree = ""; }; 74 | C10CCD38222CDF1100F6E604 /* RetValueTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RetValueTests.m; sourceTree = ""; }; 75 | C10CCD3A222CE08500F6E604 /* TRCType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCType.h; sourceTree = ""; }; 76 | C10CCD3C222CE13000F6E604 /* TRCValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TRCValue.h; sourceTree = ""; }; 77 | C118071322722A790048BB83 /* trace_bt_scan_connect.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = trace_bt_scan_connect.json; sourceTree = ""; }; 78 | C118071522722D980048BB83 /* TRCJsonDecodable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCJsonDecodable.h; sourceTree = ""; }; 79 | C118071722722EC30048BB83 /* NSDictionary+TracerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+TracerTests.m"; sourceTree = ""; }; 80 | C118071922722EE50048BB83 /* NSDictionary+Tracer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+Tracer.h"; sourceTree = ""; }; 81 | C118071A22722EE60048BB83 /* NSDictionary+Tracer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+Tracer.m"; sourceTree = ""; }; 82 | C118071D2272308C0048BB83 /* NSArray+Tracer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Tracer.h"; sourceTree = ""; }; 83 | C118071E2272308C0048BB83 /* NSArray+Tracer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Tracer.m"; sourceTree = ""; }; 84 | C1180721227230940048BB83 /* NSArray+TracerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+TracerTests.m"; sourceTree = ""; }; 85 | C1570DE022683245000F3D04 /* TRCFixtureProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCFixtureProvider.h; sourceTree = ""; }; 86 | C1570DE2226837B9000F3D04 /* FixtureProviderTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FixtureProviderTests.m; sourceTree = ""; }; 87 | C1570DE422684756000F3D04 /* LoadFromFileTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LoadFromFileTests.m; sourceTree = ""; }; 88 | C1570DE622684A00000F3D04 /* saved_trace.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = saved_trace.json; sourceTree = ""; }; 89 | C184DC3B222CA06A005F7C5D /* TRCCall+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "TRCCall+Private.h"; sourceTree = ""; }; 90 | C1A51A99223E18AF009C5F81 /* RetVoidMultiArgTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RetVoidMultiArgTests.m; sourceTree = ""; }; 91 | C1A51A9B223E1932009C5F81 /* ErrorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ErrorTests.m; sourceTree = ""; }; 92 | C1A51A9D223E218B009C5F81 /* TRCErrors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCErrors.h; sourceTree = ""; }; 93 | C1A51A9E223E218B009C5F81 /* TRCErrors.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TRCErrors.m; sourceTree = ""; }; 94 | C1A51AA1223E2222009C5F81 /* TRCErrors+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TRCErrors+Private.h"; sourceTree = ""; }; 95 | C1B89DE822251D3A00A9A73D /* TRCValue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TRCValue.m; sourceTree = ""; }; 96 | C1B89DEB22251FDB00A9A73D /* TRCValue+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TRCValue+Private.h"; sourceTree = ""; }; 97 | C1D7ABBF222102E30040808A /* Tracer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Tracer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 98 | C1D7ABC2222102E30040808A /* Tracer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tracer.h; sourceTree = ""; }; 99 | C1D7ABC3222102E30040808A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 100 | C1D7ABC8222102E30040808A /* TracerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TracerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 101 | C1D7ABCD222102E30040808A /* RetVoidSingleArgTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RetVoidSingleArgTests.m; sourceTree = ""; }; 102 | C1D7ABCF222102E30040808A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 103 | C1D7ABD9222103050040808A /* TRCRecorder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCRecorder.h; sourceTree = ""; }; 104 | C1D7ABDA222103050040808A /* TRCRecorder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TRCRecorder.m; sourceTree = ""; }; 105 | C1D7ABE1222104080040808A /* TRCAspects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TRCAspects.m; sourceTree = ""; }; 106 | C1D7ABE2222104080040808A /* TRCAspects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TRCAspects.h; sourceTree = ""; }; 107 | C1D7ABE5222104670040808A /* TRCTestTarget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCTestTarget.h; sourceTree = ""; }; 108 | C1D7ABEB222114300040808A /* NSDate+Tracer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDate+Tracer.h"; sourceTree = ""; }; 109 | C1D7ABEC222114300040808A /* NSDate+Tracer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDate+Tracer.m"; sourceTree = ""; }; 110 | C1D7ABEF222115020040808A /* TRCCall.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCCall.h; sourceTree = ""; }; 111 | C1D7ABF0222115020040808A /* TRCCall.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TRCCall.m; sourceTree = ""; }; 112 | C1D7ABF322211CE50040808A /* TRCTrace.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCTrace.h; sourceTree = ""; }; 113 | C1D7ABF422211CE50040808A /* TRCTrace.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TRCTrace.m; sourceTree = ""; }; 114 | C1D7ABF722211D1E0040808A /* TRCTrace+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "TRCTrace+Private.h"; sourceTree = ""; }; 115 | C1FED1B922252C80008FAE27 /* TRCNotNil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TRCNotNil.h; sourceTree = ""; }; 116 | /* End PBXFileReference section */ 117 | 118 | /* Begin PBXFrameworksBuildPhase section */ 119 | C1D7ABBC222102E30040808A /* Frameworks */ = { 120 | isa = PBXFrameworksBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | ); 124 | runOnlyForDeploymentPostprocessing = 0; 125 | }; 126 | C1D7ABC5222102E30040808A /* Frameworks */ = { 127 | isa = PBXFrameworksBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | C1D7ABC9222102E30040808A /* Tracer.framework in Frameworks */, 131 | ); 132 | runOnlyForDeploymentPostprocessing = 0; 133 | }; 134 | /* End PBXFrameworksBuildPhase section */ 135 | 136 | /* Begin PBXGroup section */ 137 | C106B3402221223C00504CF9 /* Private */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | C1B89DEB22251FDB00A9A73D /* TRCValue+Private.h */, 141 | C1D7ABF722211D1E0040808A /* TRCTrace+Private.h */, 142 | C1A51AA1223E2222009C5F81 /* TRCErrors+Private.h */, 143 | C184DC3B222CA06A005F7C5D /* TRCCall+Private.h */, 144 | C1FED1B922252C80008FAE27 /* TRCNotNil.h */, 145 | C1D7ABE2222104080040808A /* TRCAspects.h */, 146 | C1D7ABE1222104080040808A /* TRCAspects.m */, 147 | C1D7ABEB222114300040808A /* NSDate+Tracer.h */, 148 | C1D7ABEC222114300040808A /* NSDate+Tracer.m */, 149 | C106B34F222144EC00504CF9 /* TRCDispatchFunctions.h */, 150 | C106B350222144EC00504CF9 /* TRCDispatchFunctions.m */, 151 | C118071922722EE50048BB83 /* NSDictionary+Tracer.h */, 152 | C118071A22722EE60048BB83 /* NSDictionary+Tracer.m */, 153 | C118071D2272308C0048BB83 /* NSArray+Tracer.h */, 154 | C118071E2272308C0048BB83 /* NSArray+Tracer.m */, 155 | ); 156 | path = Private; 157 | sourceTree = ""; 158 | }; 159 | C1D7ABB5222102E30040808A = { 160 | isa = PBXGroup; 161 | children = ( 162 | C1D7ABC1222102E30040808A /* Tracer */, 163 | C1D7ABCC222102E30040808A /* TracerTests */, 164 | C1D7ABC0222102E30040808A /* Products */, 165 | ); 166 | sourceTree = ""; 167 | }; 168 | C1D7ABC0222102E30040808A /* Products */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | C1D7ABBF222102E30040808A /* Tracer.framework */, 172 | C1D7ABC8222102E30040808A /* TracerTests.xctest */, 173 | ); 174 | name = Products; 175 | sourceTree = ""; 176 | }; 177 | C1D7ABC1222102E30040808A /* Tracer */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | C1D7ABC2222102E30040808A /* Tracer.h */, 181 | C106B33E2221207300504CF9 /* TRCBlocks.h */, 182 | C106B33C22211F7A00504CF9 /* TRCJsonEncodable.h */, 183 | C118071522722D980048BB83 /* TRCJsonDecodable.h */, 184 | C1D7ABD9222103050040808A /* TRCRecorder.h */, 185 | C1D7ABDA222103050040808A /* TRCRecorder.m */, 186 | C1570DE022683245000F3D04 /* TRCFixtureProvider.h */, 187 | C106B341222126A100504CF9 /* TRCPlayer.h */, 188 | C106B342222126A100504CF9 /* TRCPlayer.m */, 189 | C10CCD3C222CE13000F6E604 /* TRCValue.h */, 190 | C1B89DE822251D3A00A9A73D /* TRCValue.m */, 191 | C1D7ABEF222115020040808A /* TRCCall.h */, 192 | C1D7ABF0222115020040808A /* TRCCall.m */, 193 | C10CCD3A222CE08500F6E604 /* TRCType.h */, 194 | C1D7ABF322211CE50040808A /* TRCTrace.h */, 195 | C1D7ABF422211CE50040808A /* TRCTrace.m */, 196 | C1A51A9D223E218B009C5F81 /* TRCErrors.h */, 197 | C1A51A9E223E218B009C5F81 /* TRCErrors.m */, 198 | C106B3402221223C00504CF9 /* Private */, 199 | C1D7ABC3222102E30040808A /* Info.plist */, 200 | ); 201 | path = Tracer; 202 | sourceTree = ""; 203 | }; 204 | C1D7ABCC222102E30040808A /* TracerTests */ = { 205 | isa = PBXGroup; 206 | children = ( 207 | C1D7ABCD222102E30040808A /* RetVoidSingleArgTests.m */, 208 | C10CCD38222CDF1100F6E604 /* RetValueTests.m */, 209 | C1A51A99223E18AF009C5F81 /* RetVoidMultiArgTests.m */, 210 | C1A51A9B223E1932009C5F81 /* ErrorTests.m */, 211 | C1570DE2226837B9000F3D04 /* FixtureProviderTests.m */, 212 | C1570DE422684756000F3D04 /* LoadFromFileTests.m */, 213 | C1D7ABE5222104670040808A /* TRCTestTarget.h */, 214 | C106B3472221445300504CF9 /* TRCTestTarget.m */, 215 | C118071722722EC30048BB83 /* NSDictionary+TracerTests.m */, 216 | C1180721227230940048BB83 /* NSArray+TracerTests.m */, 217 | C1570DE622684A00000F3D04 /* saved_trace.json */, 218 | C118071322722A790048BB83 /* trace_bt_scan_connect.json */, 219 | C1D7ABCF222102E30040808A /* Info.plist */, 220 | ); 221 | path = TracerTests; 222 | sourceTree = ""; 223 | }; 224 | /* End PBXGroup section */ 225 | 226 | /* Begin PBXHeadersBuildPhase section */ 227 | C1D7ABBA222102E30040808A /* Headers */ = { 228 | isa = PBXHeadersBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | C106B33F2221207300504CF9 /* TRCBlocks.h in Headers */, 232 | C1D7ABF522211CE50040808A /* TRCTrace.h in Headers */, 233 | C10CCD3D222CE13000F6E604 /* TRCValue.h in Headers */, 234 | C118071622722D980048BB83 /* TRCJsonDecodable.h in Headers */, 235 | C10CCD3B222CE08500F6E604 /* TRCType.h in Headers */, 236 | C1D7ABDB222103050040808A /* TRCRecorder.h in Headers */, 237 | C1570DE122683245000F3D04 /* TRCFixtureProvider.h in Headers */, 238 | C184DC3C222CA06A005F7C5D /* TRCCall+Private.h in Headers */, 239 | C106B343222126A100504CF9 /* TRCPlayer.h in Headers */, 240 | C1D7ABF1222115020040808A /* TRCCall.h in Headers */, 241 | C106B351222144EC00504CF9 /* TRCDispatchFunctions.h in Headers */, 242 | C1D7ABE4222104080040808A /* TRCAspects.h in Headers */, 243 | C1D7ABD0222102E30040808A /* Tracer.h in Headers */, 244 | C1A51A9F223E218B009C5F81 /* TRCErrors.h in Headers */, 245 | C118071B22722EE60048BB83 /* NSDictionary+Tracer.h in Headers */, 246 | C118071F2272308C0048BB83 /* NSArray+Tracer.h in Headers */, 247 | C106B34A2221446800504CF9 /* TRCTestTarget.h in Headers */, 248 | C106B33D22211F7B00504CF9 /* TRCJsonEncodable.h in Headers */, 249 | C1D7ABED222114300040808A /* NSDate+Tracer.h in Headers */, 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | }; 253 | /* End PBXHeadersBuildPhase section */ 254 | 255 | /* Begin PBXNativeTarget section */ 256 | C1D7ABBE222102E30040808A /* Tracer */ = { 257 | isa = PBXNativeTarget; 258 | buildConfigurationList = C1D7ABD3222102E30040808A /* Build configuration list for PBXNativeTarget "Tracer" */; 259 | buildPhases = ( 260 | C1D7ABBA222102E30040808A /* Headers */, 261 | C1D7ABBB222102E30040808A /* Sources */, 262 | C1D7ABBC222102E30040808A /* Frameworks */, 263 | C1D7ABBD222102E30040808A /* Resources */, 264 | ); 265 | buildRules = ( 266 | ); 267 | dependencies = ( 268 | ); 269 | name = Tracer; 270 | productName = TraceRecorder; 271 | productReference = C1D7ABBF222102E30040808A /* Tracer.framework */; 272 | productType = "com.apple.product-type.framework"; 273 | }; 274 | C1D7ABC7222102E30040808A /* TracerTests */ = { 275 | isa = PBXNativeTarget; 276 | buildConfigurationList = C1D7ABD6222102E30040808A /* Build configuration list for PBXNativeTarget "TracerTests" */; 277 | buildPhases = ( 278 | C1D7ABC4222102E30040808A /* Sources */, 279 | C1D7ABC5222102E30040808A /* Frameworks */, 280 | C1D7ABC6222102E30040808A /* Resources */, 281 | ); 282 | buildRules = ( 283 | ); 284 | dependencies = ( 285 | C1D7ABCB222102E30040808A /* PBXTargetDependency */, 286 | ); 287 | name = TracerTests; 288 | productName = TraceRecorderTests; 289 | productReference = C1D7ABC8222102E30040808A /* TracerTests.xctest */; 290 | productType = "com.apple.product-type.bundle.unit-test"; 291 | }; 292 | /* End PBXNativeTarget section */ 293 | 294 | /* Begin PBXProject section */ 295 | C1D7ABB6222102E30040808A /* Project object */ = { 296 | isa = PBXProject; 297 | attributes = { 298 | LastUpgradeCheck = 1010; 299 | ORGANIZATIONNAME = tracer; 300 | TargetAttributes = { 301 | C1D7ABBE222102E30040808A = { 302 | CreatedOnToolsVersion = 10.1; 303 | }; 304 | C1D7ABC7222102E30040808A = { 305 | CreatedOnToolsVersion = 10.1; 306 | }; 307 | }; 308 | }; 309 | buildConfigurationList = C1D7ABB9222102E30040808A /* Build configuration list for PBXProject "Tracer" */; 310 | compatibilityVersion = "Xcode 9.3"; 311 | developmentRegion = en; 312 | hasScannedForEncodings = 0; 313 | knownRegions = ( 314 | en, 315 | ); 316 | mainGroup = C1D7ABB5222102E30040808A; 317 | productRefGroup = C1D7ABC0222102E30040808A /* Products */; 318 | projectDirPath = ""; 319 | projectRoot = ""; 320 | targets = ( 321 | C1D7ABBE222102E30040808A /* Tracer */, 322 | C1D7ABC7222102E30040808A /* TracerTests */, 323 | ); 324 | }; 325 | /* End PBXProject section */ 326 | 327 | /* Begin PBXResourcesBuildPhase section */ 328 | C1D7ABBD222102E30040808A /* Resources */ = { 329 | isa = PBXResourcesBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | ); 333 | runOnlyForDeploymentPostprocessing = 0; 334 | }; 335 | C1D7ABC6222102E30040808A /* Resources */ = { 336 | isa = PBXResourcesBuildPhase; 337 | buildActionMask = 2147483647; 338 | files = ( 339 | C1570DE722684A01000F3D04 /* saved_trace.json in Resources */, 340 | C118071422722A790048BB83 /* trace_bt_scan_connect.json in Resources */, 341 | ); 342 | runOnlyForDeploymentPostprocessing = 0; 343 | }; 344 | /* End PBXResourcesBuildPhase section */ 345 | 346 | /* Begin PBXSourcesBuildPhase section */ 347 | C1D7ABBB222102E30040808A /* Sources */ = { 348 | isa = PBXSourcesBuildPhase; 349 | buildActionMask = 2147483647; 350 | files = ( 351 | C1D7ABF2222115020040808A /* TRCCall.m in Sources */, 352 | C1D7ABF622211CE50040808A /* TRCTrace.m in Sources */, 353 | C106B3492221446500504CF9 /* TRCTestTarget.m in Sources */, 354 | C106B352222144EC00504CF9 /* TRCDispatchFunctions.m in Sources */, 355 | C1D7ABDC222103050040808A /* TRCRecorder.m in Sources */, 356 | C1D7ABE3222104080040808A /* TRCAspects.m in Sources */, 357 | C1A51AA0223E218B009C5F81 /* TRCErrors.m in Sources */, 358 | C11807202272308C0048BB83 /* NSArray+Tracer.m in Sources */, 359 | C106B344222126A100504CF9 /* TRCPlayer.m in Sources */, 360 | C1D7ABEE222114300040808A /* NSDate+Tracer.m in Sources */, 361 | C1B89DEA22251D3A00A9A73D /* TRCValue.m in Sources */, 362 | C118071C22722EE60048BB83 /* NSDictionary+Tracer.m in Sources */, 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | }; 366 | C1D7ABC4222102E30040808A /* Sources */ = { 367 | isa = PBXSourcesBuildPhase; 368 | buildActionMask = 2147483647; 369 | files = ( 370 | C118071822722EC30048BB83 /* NSDictionary+TracerTests.m in Sources */, 371 | C106B3482221445300504CF9 /* TRCTestTarget.m in Sources */, 372 | C1D7ABCE222102E30040808A /* RetVoidSingleArgTests.m in Sources */, 373 | C1570DE3226837B9000F3D04 /* FixtureProviderTests.m in Sources */, 374 | C10CCD39222CDF1100F6E604 /* RetValueTests.m in Sources */, 375 | C1180722227230940048BB83 /* NSArray+TracerTests.m in Sources */, 376 | C106B353222144F800504CF9 /* TRCDispatchFunctions.m in Sources */, 377 | C1570DE522684756000F3D04 /* LoadFromFileTests.m in Sources */, 378 | C1A51A9A223E18AF009C5F81 /* RetVoidMultiArgTests.m in Sources */, 379 | C1A51A9C223E1932009C5F81 /* ErrorTests.m in Sources */, 380 | ); 381 | runOnlyForDeploymentPostprocessing = 0; 382 | }; 383 | /* End PBXSourcesBuildPhase section */ 384 | 385 | /* Begin PBXTargetDependency section */ 386 | C1D7ABCB222102E30040808A /* PBXTargetDependency */ = { 387 | isa = PBXTargetDependency; 388 | target = C1D7ABBE222102E30040808A /* Tracer */; 389 | targetProxy = C1D7ABCA222102E30040808A /* PBXContainerItemProxy */; 390 | }; 391 | /* End PBXTargetDependency section */ 392 | 393 | /* Begin XCBuildConfiguration section */ 394 | C1D7ABD1222102E30040808A /* Debug */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | CLANG_ANALYZER_NONNULL = YES; 399 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 400 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 401 | CLANG_CXX_LIBRARY = "libc++"; 402 | CLANG_ENABLE_MODULES = YES; 403 | CLANG_ENABLE_OBJC_ARC = YES; 404 | CLANG_ENABLE_OBJC_WEAK = YES; 405 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 406 | CLANG_WARN_BOOL_CONVERSION = YES; 407 | CLANG_WARN_COMMA = YES; 408 | CLANG_WARN_CONSTANT_CONVERSION = YES; 409 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 410 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 411 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 412 | CLANG_WARN_EMPTY_BODY = YES; 413 | CLANG_WARN_ENUM_CONVERSION = YES; 414 | CLANG_WARN_INFINITE_RECURSION = YES; 415 | CLANG_WARN_INT_CONVERSION = YES; 416 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 417 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 418 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 419 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 420 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 421 | CLANG_WARN_STRICT_PROTOTYPES = YES; 422 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 423 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 424 | CLANG_WARN_UNREACHABLE_CODE = YES; 425 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 426 | CODE_SIGN_IDENTITY = "iPhone Developer"; 427 | COPY_PHASE_STRIP = NO; 428 | CURRENT_PROJECT_VERSION = 1; 429 | DEBUG_INFORMATION_FORMAT = dwarf; 430 | ENABLE_STRICT_OBJC_MSGSEND = YES; 431 | ENABLE_TESTABILITY = YES; 432 | GCC_C_LANGUAGE_STANDARD = gnu11; 433 | GCC_DYNAMIC_NO_PIC = NO; 434 | GCC_NO_COMMON_BLOCKS = YES; 435 | GCC_OPTIMIZATION_LEVEL = 0; 436 | GCC_PREPROCESSOR_DEFINITIONS = ( 437 | "DEBUG=1", 438 | "$(inherited)", 439 | ); 440 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 441 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 442 | GCC_WARN_UNDECLARED_SELECTOR = YES; 443 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 444 | GCC_WARN_UNUSED_FUNCTION = YES; 445 | GCC_WARN_UNUSED_VARIABLE = YES; 446 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 447 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 448 | MTL_FAST_MATH = YES; 449 | ONLY_ACTIVE_ARCH = YES; 450 | SDKROOT = iphoneos; 451 | VERSIONING_SYSTEM = "apple-generic"; 452 | VERSION_INFO_PREFIX = ""; 453 | }; 454 | name = Debug; 455 | }; 456 | C1D7ABD2222102E30040808A /* Release */ = { 457 | isa = XCBuildConfiguration; 458 | buildSettings = { 459 | ALWAYS_SEARCH_USER_PATHS = NO; 460 | CLANG_ANALYZER_NONNULL = YES; 461 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 462 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 463 | CLANG_CXX_LIBRARY = "libc++"; 464 | CLANG_ENABLE_MODULES = YES; 465 | CLANG_ENABLE_OBJC_ARC = YES; 466 | CLANG_ENABLE_OBJC_WEAK = YES; 467 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 468 | CLANG_WARN_BOOL_CONVERSION = YES; 469 | CLANG_WARN_COMMA = YES; 470 | CLANG_WARN_CONSTANT_CONVERSION = YES; 471 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 472 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 473 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 474 | CLANG_WARN_EMPTY_BODY = YES; 475 | CLANG_WARN_ENUM_CONVERSION = YES; 476 | CLANG_WARN_INFINITE_RECURSION = YES; 477 | CLANG_WARN_INT_CONVERSION = YES; 478 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 479 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 480 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 481 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 482 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 483 | CLANG_WARN_STRICT_PROTOTYPES = YES; 484 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 485 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 486 | CLANG_WARN_UNREACHABLE_CODE = YES; 487 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 488 | CODE_SIGN_IDENTITY = "iPhone Developer"; 489 | COPY_PHASE_STRIP = NO; 490 | CURRENT_PROJECT_VERSION = 1; 491 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 492 | ENABLE_NS_ASSERTIONS = NO; 493 | ENABLE_STRICT_OBJC_MSGSEND = YES; 494 | GCC_C_LANGUAGE_STANDARD = gnu11; 495 | GCC_NO_COMMON_BLOCKS = YES; 496 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 497 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 498 | GCC_WARN_UNDECLARED_SELECTOR = YES; 499 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 500 | GCC_WARN_UNUSED_FUNCTION = YES; 501 | GCC_WARN_UNUSED_VARIABLE = YES; 502 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 503 | MTL_ENABLE_DEBUG_INFO = NO; 504 | MTL_FAST_MATH = YES; 505 | SDKROOT = iphoneos; 506 | VALIDATE_PRODUCT = YES; 507 | VERSIONING_SYSTEM = "apple-generic"; 508 | VERSION_INFO_PREFIX = ""; 509 | }; 510 | name = Release; 511 | }; 512 | C1D7ABD4222102E30040808A /* Debug */ = { 513 | isa = XCBuildConfiguration; 514 | buildSettings = { 515 | CODE_SIGN_IDENTITY = ""; 516 | CODE_SIGN_STYLE = Automatic; 517 | DEFINES_MODULE = YES; 518 | DEVELOPMENT_TEAM = M6UK8SVLX3; 519 | DYLIB_COMPATIBILITY_VERSION = 1; 520 | DYLIB_CURRENT_VERSION = 1; 521 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 522 | INFOPLIST_FILE = Tracer/Info.plist; 523 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 524 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 525 | LD_RUNPATH_SEARCH_PATHS = ( 526 | "$(inherited)", 527 | "@executable_path/Frameworks", 528 | "@loader_path/Frameworks", 529 | ); 530 | PRODUCT_BUNDLE_IDENTIFIER = tracer.Tracer; 531 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 532 | SKIP_INSTALL = YES; 533 | TARGETED_DEVICE_FAMILY = "1,2"; 534 | }; 535 | name = Debug; 536 | }; 537 | C1D7ABD5222102E30040808A /* Release */ = { 538 | isa = XCBuildConfiguration; 539 | buildSettings = { 540 | CODE_SIGN_IDENTITY = ""; 541 | CODE_SIGN_STYLE = Automatic; 542 | DEFINES_MODULE = YES; 543 | DEVELOPMENT_TEAM = M6UK8SVLX3; 544 | DYLIB_COMPATIBILITY_VERSION = 1; 545 | DYLIB_CURRENT_VERSION = 1; 546 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 547 | INFOPLIST_FILE = Tracer/Info.plist; 548 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 549 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 550 | LD_RUNPATH_SEARCH_PATHS = ( 551 | "$(inherited)", 552 | "@executable_path/Frameworks", 553 | "@loader_path/Frameworks", 554 | ); 555 | PRODUCT_BUNDLE_IDENTIFIER = tracer.Tracer; 556 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 557 | SKIP_INSTALL = YES; 558 | TARGETED_DEVICE_FAMILY = "1,2"; 559 | }; 560 | name = Release; 561 | }; 562 | C1D7ABD7222102E30040808A /* Debug */ = { 563 | isa = XCBuildConfiguration; 564 | buildSettings = { 565 | CODE_SIGN_STYLE = Automatic; 566 | DEVELOPMENT_TEAM = M6UK8SVLX3; 567 | INFOPLIST_FILE = TracerTests/Info.plist; 568 | LD_RUNPATH_SEARCH_PATHS = ( 569 | "$(inherited)", 570 | "@executable_path/Frameworks", 571 | "@loader_path/Frameworks", 572 | ); 573 | PRODUCT_BUNDLE_IDENTIFIER = tracer.TracerTests; 574 | PRODUCT_NAME = "$(TARGET_NAME)"; 575 | TARGETED_DEVICE_FAMILY = "1,2"; 576 | }; 577 | name = Debug; 578 | }; 579 | C1D7ABD8222102E30040808A /* Release */ = { 580 | isa = XCBuildConfiguration; 581 | buildSettings = { 582 | CODE_SIGN_STYLE = Automatic; 583 | DEVELOPMENT_TEAM = M6UK8SVLX3; 584 | INFOPLIST_FILE = TracerTests/Info.plist; 585 | LD_RUNPATH_SEARCH_PATHS = ( 586 | "$(inherited)", 587 | "@executable_path/Frameworks", 588 | "@loader_path/Frameworks", 589 | ); 590 | PRODUCT_BUNDLE_IDENTIFIER = tracer.TracerTests; 591 | PRODUCT_NAME = "$(TARGET_NAME)"; 592 | TARGETED_DEVICE_FAMILY = "1,2"; 593 | }; 594 | name = Release; 595 | }; 596 | /* End XCBuildConfiguration section */ 597 | 598 | /* Begin XCConfigurationList section */ 599 | C1D7ABB9222102E30040808A /* Build configuration list for PBXProject "Tracer" */ = { 600 | isa = XCConfigurationList; 601 | buildConfigurations = ( 602 | C1D7ABD1222102E30040808A /* Debug */, 603 | C1D7ABD2222102E30040808A /* Release */, 604 | ); 605 | defaultConfigurationIsVisible = 0; 606 | defaultConfigurationName = Release; 607 | }; 608 | C1D7ABD3222102E30040808A /* Build configuration list for PBXNativeTarget "Tracer" */ = { 609 | isa = XCConfigurationList; 610 | buildConfigurations = ( 611 | C1D7ABD4222102E30040808A /* Debug */, 612 | C1D7ABD5222102E30040808A /* Release */, 613 | ); 614 | defaultConfigurationIsVisible = 0; 615 | defaultConfigurationName = Release; 616 | }; 617 | C1D7ABD6222102E30040808A /* Build configuration list for PBXNativeTarget "TracerTests" */ = { 618 | isa = XCConfigurationList; 619 | buildConfigurations = ( 620 | C1D7ABD7222102E30040808A /* Debug */, 621 | C1D7ABD8222102E30040808A /* Release */, 622 | ); 623 | defaultConfigurationIsVisible = 0; 624 | defaultConfigurationName = Release; 625 | }; 626 | /* End XCConfigurationList section */ 627 | }; 628 | rootObject = C1D7ABB6222102E30040808A /* Project object */; 629 | } 630 | -------------------------------------------------------------------------------- /Tracer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tracer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tracer.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Tracer.xcodeproj/xcshareddata/xcschemes/Tracer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Tracer/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe/tracer-objc/03abf6d5bfb403114ac9fd80d6aa1f5b49662d49/Tracer/.DS_Store -------------------------------------------------------------------------------- /Tracer/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 | 22 | 23 | -------------------------------------------------------------------------------- /Tracer/Private/NSArray+Tracer.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Tracer.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 7/27/17. 6 | // Copyright © 2017 Stripe. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSArray (Tracer) 14 | 15 | - (nullable NSArray *)trc_safeSubarrayWithRange:(NSRange)range; 16 | 17 | - (nullable id)trc_safeObjectAtIndex:(NSInteger)index; 18 | 19 | - (NSArray *)trc_arrayByRemovingNulls; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /Tracer/Private/NSArray+Tracer.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Tracer.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 7/27/17. 6 | // Copyright © 2017 Stripe. All rights reserved. 7 | // 8 | 9 | #import "NSArray+Tracer.h" 10 | #import "NSDictionary+Tracer.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @implementation NSArray (Tracer) 15 | 16 | - (nullable NSArray *)trc_safeSubarrayWithRange:(NSRange)range { 17 | if (range.location + range.length > self.count || 18 | range.location > self.count) { 19 | return nil; 20 | } 21 | return [self subarrayWithRange:range]; 22 | } 23 | 24 | - (nullable id)trc_safeObjectAtIndex:(NSInteger)index { 25 | if (index + 1 > (NSInteger)self.count || index < 0) { 26 | return nil; 27 | } 28 | return self[index]; 29 | } 30 | 31 | - (NSArray *)trc_arrayByRemovingNulls { 32 | NSMutableArray *result = [[NSMutableArray alloc] init]; 33 | 34 | for (id obj in self) { 35 | if ([obj isKindOfClass:[NSArray class]]) { 36 | // Save array after removing any null values 37 | [result addObject:[(NSArray *)obj trc_arrayByRemovingNulls]]; 38 | } 39 | else if ([obj isKindOfClass:[NSDictionary class]]) { 40 | // Save dictionary after removing any null values 41 | [result addObject:[(NSDictionary *)obj trc_dictionaryByRemovingNulls]]; 42 | } 43 | else if ([obj isKindOfClass:[NSNull class]]) { 44 | // Skip null value 45 | } 46 | else { 47 | // Save other value 48 | [result addObject:obj]; 49 | } 50 | } 51 | 52 | // Make immutable copy 53 | return [result copy]; 54 | } 55 | 56 | @end 57 | 58 | NS_ASSUME_NONNULL_END 59 | -------------------------------------------------------------------------------- /Tracer/Private/NSDate+Tracer.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Tracer.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSDate (Tracer) 14 | 15 | // unsigned milliseconds since the given date 16 | - (NSUInteger)trc_millisSinceDate:(NSDate *)date; 17 | 18 | // epoch time in milliseconds 19 | - (NSUInteger)trc_millisSince1970; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /Tracer/Private/NSDate+Tracer.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+Tracer.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "NSDate+Tracer.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @implementation NSDate (Tracer) 14 | 15 | - (NSUInteger)trc_millisSinceDate:(NSDate *)date { 16 | return (NSUInteger)ABS(ceil([self timeIntervalSinceDate:date]*1000)); 17 | } 18 | 19 | - (NSUInteger)trc_millisSince1970 { 20 | NSDate *date = [NSDate dateWithTimeIntervalSince1970:0]; 21 | return [self trc_millisSinceDate:date]; 22 | } 23 | 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | 28 | -------------------------------------------------------------------------------- /Tracer/Private/NSDictionary+Tracer.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+Tracer.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 7/26/17. 6 | // Copyright © 2017 Stripe. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TRCJsonDecodable.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface NSDictionary (Tracer) 16 | 17 | - (NSDictionary *)trc_dictionaryByRemovingNulls; 18 | 19 | // Getters 20 | 21 | - (nullable NSArray *)trc_arrayForKey:(NSString *)key; 22 | 23 | /** 24 | Always return NSMutableArray, containing any/all objects that could 25 | deserialized from the array at `key`, using the `deserializer`'s class. 26 | 27 | Any objects that can't be deserialized are dropped silently, and the returned 28 | array might be empty. 29 | */ 30 | - (NSMutableArray *)trc_arrayForKey:(NSString *)key deserializedAs:(Class)deserializer; 31 | 32 | - (nullable NSNumber *)trc_boxedBoolForKey:(NSString *)key; 33 | 34 | - (BOOL)trc_boolForKey:(NSString *)key or:(BOOL)defaultValue; 35 | 36 | - (nullable NSDate *)trc_dateForKey:(NSString *)key; 37 | 38 | - (nullable NSDictionary *)trc_dictionaryForKey:(NSString *)key; 39 | 40 | - (NSInteger)trc_intForKey:(NSString *)key or:(NSInteger)defaultValue; 41 | 42 | - (nullable NSNumber *)trc_numberForKey:(NSString *)key; 43 | 44 | - (nullable NSString *)trc_stringForKey:(NSString *)key; 45 | 46 | - (nullable NSURL *)trc_urlForKey:(NSString *)key; 47 | 48 | @end 49 | 50 | NS_ASSUME_NONNULL_END 51 | 52 | -------------------------------------------------------------------------------- /Tracer/Private/NSDictionary+Tracer.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+Tracer.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 7/26/17. 6 | // Copyright © 2017 Stripe. All rights reserved. 7 | // 8 | 9 | #import "NSDictionary+Tracer.h" 10 | 11 | #import "NSArray+Tracer.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @implementation NSDictionary (Tracer) 16 | 17 | - (NSDictionary *)trc_dictionaryByRemovingNulls { 18 | NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; 19 | 20 | [self enumerateKeysAndObjectsUsingBlock:^(id key, id obj, __unused BOOL *stop) { 21 | if ([obj isKindOfClass:[NSArray class]]) { 22 | // Save array after removing any null values 23 | result[key] = [(NSArray *)obj trc_arrayByRemovingNulls]; 24 | } 25 | else if ([obj isKindOfClass:[NSDictionary class]]) { 26 | // Save dictionary after removing any null values 27 | result[key] = [(NSDictionary *)obj trc_dictionaryByRemovingNulls]; 28 | } 29 | else if ([obj isKindOfClass:[NSNull class]]) { 30 | // Skip null value 31 | } 32 | else { 33 | // Save other value 34 | result[key] = obj; 35 | } 36 | }]; 37 | 38 | // Make immutable copy 39 | return [result copy]; 40 | } 41 | 42 | #pragma mark - Getters 43 | 44 | - (nullable NSArray *)trc_arrayForKey:(NSString *)key { 45 | id value = self[key]; 46 | if (value && [value isKindOfClass:[NSArray class]]) { 47 | return value; 48 | } 49 | return nil; 50 | } 51 | 52 | - (NSMutableArray *)trc_arrayForKey:(NSString *)key deserializedAs:(Class)deserializer { 53 | NSMutableArray *array = [NSMutableArray new]; 54 | 55 | for (id element in [self trc_arrayForKey:key]) { 56 | if ([element isKindOfClass:[NSDictionary class]]) { 57 | id decoded = [deserializer decodedObjectFromJson:element]; 58 | if (decoded) { 59 | [array addObject:decoded]; 60 | } 61 | } 62 | } 63 | 64 | return array; 65 | } 66 | 67 | - (nullable NSNumber *)trc_boxedBoolForKey:(NSString *)key { 68 | id value = self[key]; 69 | if (value) { 70 | if ([value isKindOfClass:[NSNumber class]]) { 71 | return @([value boolValue]); 72 | } 73 | if ([value isKindOfClass:[NSString class]]) { 74 | NSString *string = [(NSString *)value lowercaseString]; 75 | // boolValue on NSString is true for "Y", "y", "T", "t", or 1-9 76 | if ([string isEqualToString:@"true"] || [string boolValue]) { 77 | return @YES; 78 | } 79 | else { 80 | return @NO; 81 | } 82 | } 83 | } 84 | return nil; 85 | } 86 | 87 | - (BOOL)trc_boolForKey:(NSString *)key or:(BOOL)defaultValue { 88 | NSNumber *boxedBool = [self trc_boxedBoolForKey:key]; 89 | if (boxedBool != nil) { 90 | return [boxedBool boolValue]; 91 | } 92 | return defaultValue; 93 | } 94 | 95 | - (nullable NSDate *)trc_dateForKey:(NSString *)key { 96 | id value = self[key]; 97 | if (value && 98 | ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]])) { 99 | double timeInterval = [value doubleValue]; 100 | return [NSDate dateWithTimeIntervalSince1970:timeInterval]; 101 | } 102 | return nil; 103 | } 104 | 105 | - (nullable NSDictionary *)trc_dictionaryForKey:(NSString *)key { 106 | id value = self[key]; 107 | if (value && [value isKindOfClass:[NSDictionary class]]) { 108 | return value; 109 | } 110 | return nil; 111 | } 112 | 113 | - (NSInteger)trc_intForKey:(NSString *)key or:(NSInteger)defaultValue { 114 | id value = self[key]; 115 | if (value && 116 | ([value isKindOfClass:[NSNumber class]] || [value isKindOfClass:[NSString class]])) { 117 | return [value integerValue]; 118 | } 119 | return defaultValue; 120 | } 121 | 122 | - (nullable NSNumber *)trc_numberForKey:(NSString *)key { 123 | id value = self[key]; 124 | if (value && [value isKindOfClass:[NSNumber class]]) { 125 | return value; 126 | } 127 | return nil; 128 | } 129 | 130 | - (nullable NSString *)trc_stringForKey:(NSString *)key { 131 | id value = self[key]; 132 | if (value && [value isKindOfClass:[NSString class]]) { 133 | return value; 134 | } 135 | return nil; 136 | } 137 | 138 | - (nullable NSURL *)trc_urlForKey:(NSString *)key { 139 | id value = self[key]; 140 | if (value && [value isKindOfClass:[NSString class]]) { 141 | return [NSURL URLWithString:value]; 142 | } 143 | return nil; 144 | } 145 | 146 | #pragma mark - JsonDecodable 147 | 148 | + (nullable instancetype)decodedObjectFromJson:(nullable NSDictionary *)json { 149 | return json; 150 | } 151 | 152 | @end 153 | 154 | NS_ASSUME_NONNULL_END 155 | 156 | -------------------------------------------------------------------------------- /Tracer/Private/TRCAspects.h: -------------------------------------------------------------------------------- 1 | // 2 | // Aspects.h 3 | // Aspects - A delightful, simple library for aspect oriented programming. 4 | // 5 | // Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license. 6 | // 7 | 8 | #import 9 | 10 | typedef NS_OPTIONS(NSUInteger, TRCAspectOptions) { 11 | /// Called after the original implementation (default) 12 | TRCAspectPositionAfter = 0, 13 | /// Will replace the original implementation. 14 | TRCAspectPositionInstead = 1, 15 | /// Called before the original implementation. 16 | TRCAspectPositionBefore = 2, 17 | 18 | /// Will remove the hook after the first execution. 19 | TRCAspectOptionAutomaticRemoval = 1 << 3 20 | }; 21 | 22 | /// Opaque Aspect Token that allows to deregister the hook. 23 | @protocol TRCAspectToken 24 | 25 | /// Deregisters an aspect. 26 | /// @return YES if deregistration is successful, otherwise NO. 27 | - (BOOL)remove; 28 | 29 | @end 30 | 31 | /// The AspectInfo protocol is the first parameter of our block syntax. 32 | @protocol TRCAspectInfo 33 | 34 | /// The instance that is currently hooked. 35 | - (id)instance; 36 | 37 | /// The original invocation of the hooked method. 38 | - (NSInvocation *)originalInvocation; 39 | 40 | /// All method arguments, boxed. This is lazily evaluated. 41 | - (NSArray *)arguments; 42 | 43 | @end 44 | 45 | /** 46 | Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second. 47 | 48 | Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe. 49 | */ 50 | @interface NSObject (TRCAspects) 51 | 52 | /// Adds a block of code before/instead/after the current `selector` for a specific class. 53 | /// 54 | /// @param block Aspects replicates the type signature of the method being hooked. 55 | /// The first parameter will be `id`, followed by all parameters of the method. 56 | /// These parameters are optional and will be filled to match the block signature. 57 | /// You can even use an empty block, or one that simple gets `id`. 58 | /// 59 | /// @note Hooking static methods is not supported. 60 | /// @return A token which allows to later deregister the aspect. 61 | + (id)trc_aspect_hookSelector:(SEL)selector 62 | withOptions:(TRCAspectOptions)options 63 | usingBlock:(id)block 64 | error:(NSError **)error; 65 | 66 | /// Adds a block of code before/instead/after the current `selector` for a specific instance. 67 | - (id)trc_aspect_hookSelector:(SEL)selector 68 | withOptions:(TRCAspectOptions)options 69 | usingBlock:(id)block 70 | error:(NSError **)error; 71 | 72 | @end 73 | 74 | void linkAspectsCategory(void); 75 | 76 | 77 | typedef NS_ENUM(NSUInteger, TRCAspectErrorCode) { 78 | /// Selectors like release, retain, autorelease are blacklisted. 79 | TRCAspectErrorSelectorBlacklisted, 80 | /// Selector could not be found. 81 | TRCAspectErrorDoesNotRespondToSelector, 82 | /// When hooking dealloc, only AspectPositionBefore is allowed. 83 | TRCAspectErrorSelectorDeallocPosition, 84 | /// Statically hooking the same method in subclasses is not allowed. 85 | TRCAspectErrorSelectorAlreadyHookedInClassHierarchy, 86 | /// The runtime failed creating a class pair. 87 | TRCAspectErrorFailedToAllocateClassPair, 88 | /// The block misses compile time signature info and can't be called. 89 | TRCAspectErrorMissingBlockSignature, 90 | /// The block signature does not match the method or is too large. 91 | TRCAspectErrorIncompatibleBlockSignature, 92 | 93 | /// (for removing) The object hooked is already deallocated. 94 | TRCAspectErrorRemoveObjectAlreadyDeallocated = 100 95 | }; 96 | 97 | extern NSString *const TRCAspectErrorDomain; 98 | -------------------------------------------------------------------------------- /Tracer/Private/TRCCall+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCCall+Private.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCTrace.h" 10 | #import "TRCCall.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface TRCCall (Private) 15 | 16 | /** 17 | Internal id used to reference the call 18 | */ 19 | - (NSString *)internalId; 20 | 21 | /** 22 | might be able to consolidate some of these arguments 23 | 24 | @param selector selector reference, before hooking 25 | @param invocation invocation, from TRCAspects 26 | @param arguments arguments, built from method sig and invocation 27 | @param millis timestamp for method call 28 | */ 29 | - (instancetype)initWithSelector:(SEL)selector 30 | invocation:(NSInvocation *)invocation 31 | arguments:(NSArray*)arguments 32 | millis:(NSUInteger)millis; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /Tracer/Private/TRCDispatchFunctions.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestDispatchFunctions.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | __BEGIN_DECLS 13 | 14 | typedef void (^TestVoidBlock)(void); 15 | void trcDispatchToMainThreadIfNecessary(dispatch_block_t block); 16 | void trcDispatchToMainAfter(NSTimeInterval timeInterval, TestVoidBlock block); 17 | 18 | __END_DECLS 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Tracer/Private/TRCDispatchFunctions.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestDispatchFunctions.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCDispatchFunctions.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | void trcDispatchToMainThreadIfNecessary(dispatch_block_t block) { 14 | if ([NSThread isMainThread]) { 15 | block(); 16 | } 17 | else { 18 | dispatch_async(dispatch_get_main_queue(), block); 19 | } 20 | } 21 | 22 | void trcDispatchToMainAfter(NSTimeInterval timeInterval, TestVoidBlock block) { 23 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)), dispatch_get_main_queue(), block); 24 | } 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /Tracer/Private/TRCErrors+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCError+Private.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 3/16/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCErrors.h" 10 | 11 | @class TRCCall; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface TRCErrors: NSError 16 | 17 | + (NSError *)buildError:(TRCError)errorCode call:(TRCCall *)call message:(NSString *)message; 18 | + (NSError *)buildError:(TRCError)errorCode; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /Tracer/Private/TRCNotNil.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCNotNil.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 1/11/18. 6 | // Copyright © 2018 Stripe. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | Based on RBBNotNil from 15 | https://gist.github.com/robb/d55b72d62d32deaee5fa 16 | */ 17 | 18 | // We purposefully don't have a matching @implementation. 19 | // We don't want +asNonnull to ever actually be called 20 | // because that will add a lot of overhead to every TRCNotNil 21 | // and we want TRCNotNil to be very cheap. 22 | // If there is no @implementation, then if the +asNonnull is 23 | // actually called, we'll get a linker error complaining about 24 | // the lack of @implementation. 25 | @interface SCPBox <__covariant Type> 26 | 27 | // This as a class method so you don't need to 28 | // declare an unused lvalue just for a __typeof 29 | + (Type _Nonnull)asNonnull; 30 | 31 | @end 32 | 33 | /*! 34 | * @define TRCNotNil(V) 35 | * Converts an Objective-C object expression from _Nullable to _Nonnull. 36 | * Crashes if it receives a nil! We must crash or else we'll receive 37 | * static analyzer warnings when archiving. I think in Release mode, 38 | * the compiler ignores the _Nonnull cast. 39 | * @param V a _Nullable Objective-C object expression 40 | */ 41 | #define TRCNotNil(V) \ 42 | _Pragma("clang diagnostic push") \ 43 | _Pragma("clang diagnostic ignored \"-Wgnu-statement-expression\"") \ 44 | ({ \ 45 | __typeof__(V) __nullableV = V; \ 46 | NSCAssert(__nullableV, @"Expected '%@' not to be nil.", @#V); \ 47 | if (!__nullableV) { \ 48 | abort(); \ 49 | } \ 50 | (__typeof([SCPBox<__typeof(V)> asNonnull]))__nullableV; \ 51 | }) \ 52 | _Pragma("clang diagnostic pop") 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /Tracer/Private/TRCTrace+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCTrace+Private.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCTrace.h" 10 | #import "TRCCall.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface TRCTrace (Private) 15 | 16 | /** 17 | Internal id used to reference the trace 18 | */ 19 | - (NSString *)internalId; 20 | 21 | - (instancetype)initWithProtocol:(Protocol *)protocol; 22 | 23 | - (void)addCall:(TRCCall *)call; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /Tracer/Private/TRCValue+Private.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCArgument+Private.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/25/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCValue.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface TRCValue (Private) 14 | 15 | @property (atomic, strong, nullable, readwrite) id objectValue; 16 | 17 | /** 18 | https://nshipster.com/type-encodings/ 19 | */ 20 | - (instancetype)initWithTypeEncoding:(NSString *)encoding 21 | boxedArgument:(id)boxedArgument; 22 | 23 | + (nullable NSString *)stringFromType:(TRCType)type; 24 | + (TRCType)typeWithEncoding:(NSString *)encodingString; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /Tracer/TRCBlocks.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCBlocks.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class TRCTrace; 14 | 15 | NS_SWIFT_NAME(TraceCompletionBlock) 16 | typedef void (^TRCTraceCompletionBlock)(TRCTrace *__nullable trace, NSError *__nullable error); 17 | 18 | NS_SWIFT_NAME(ErrorCompletionBlock) 19 | typedef void (^TRCErrorCompletionBlock)(NSError *__nullable error); 20 | 21 | NS_ASSUME_NONNULL_END 22 | 23 | -------------------------------------------------------------------------------- /Tracer/TRCCall.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCRecordedInvocation.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TRCJsonDecodable.h" 12 | #import "TRCJsonEncodable.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @class TRCValue; 17 | 18 | /** 19 | A trace call. 20 | */ 21 | NS_SWIFT_NAME(TraceCall) 22 | @interface TRCCall : NSObject 23 | 24 | /** 25 | The method, as a string. 26 | */ 27 | @property (atomic, readonly) NSString *method; 28 | 29 | /** 30 | The arguments of the method. 31 | */ 32 | @property (atomic, readonly) NSArray*arguments; 33 | 34 | /** 35 | The epoch time. 36 | */ 37 | @property (atomic, readonly) NSUInteger millis; 38 | 39 | /** 40 | The return value. 41 | */ 42 | @property (atomic, readonly) TRCValue *returnValue; 43 | 44 | - (instancetype)init NS_UNAVAILABLE; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /Tracer/TRCCall.m: -------------------------------------------------------------------------------- 1 | // 2 | // TRCMethodCall.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCCall.h" 10 | 11 | #import 12 | 13 | #import "TRCAspects.h" 14 | #import "TRCValue+Private.h" 15 | #import "NSDictionary+Tracer.h" 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | @interface TRCCall () 20 | 21 | @property (atomic, strong, readwrite) NSString *method; 22 | @property (atomic, strong, readwrite) NSArray*arguments; 23 | @property (atomic, strong, readwrite) TRCValue *returnValue; 24 | @property (atomic, assign, readwrite) NSUInteger millis; 25 | 26 | @end 27 | 28 | @implementation TRCCall 29 | 30 | - (instancetype)initWithSelector:(SEL)selector 31 | invocation:(NSInvocation *)invocation 32 | arguments:(NSArray*)arguments 33 | millis:(NSUInteger)millis { 34 | self = [super init]; 35 | if (self) { 36 | _method = NSStringFromSelector(selector); 37 | _arguments = arguments; 38 | _millis = millis; 39 | _returnValue = [[self class] buildReturnValue:invocation]; 40 | } 41 | return self; 42 | } 43 | 44 | - (NSString *)internalId { 45 | return [NSString stringWithFormat:@"%@_%lu", self.method, self.millis]; 46 | } 47 | 48 | + (nullable instancetype)decodedObjectFromJson:(nullable NSDictionary *)json { 49 | // precheck 50 | if (!json) { 51 | return nil; 52 | } 53 | NSDictionary *dict = [json trc_dictionaryByRemovingNulls]; 54 | NSString *object = [dict trc_stringForKey:@"trace_object"]; 55 | if (![object isEqualToString:@"call"]) { 56 | return nil; 57 | } 58 | 59 | // props 60 | NSString *method = [dict trc_stringForKey:@"method"]; 61 | NSNumber *startMs = [dict trc_numberForKey:@"start_ms"]; 62 | NSDictionary *rawReturnValue = [dict trc_dictionaryForKey:@"return_value"]; 63 | NSArray *rawArguments = [dict trc_arrayForKey:@"arguments"]; 64 | if (!method || !startMs || !rawReturnValue || !rawArguments) { 65 | return nil; 66 | } 67 | 68 | NSMutableArray *arguments = [NSMutableArray new]; 69 | for (NSDictionary *rawValue in rawArguments) { 70 | TRCValue *value = [TRCValue decodedObjectFromJson:rawValue]; 71 | if (value != nil) { 72 | [arguments addObject:value]; 73 | } 74 | } 75 | TRCValue *returnValue = [TRCValue decodedObjectFromJson:rawReturnValue]; 76 | 77 | // assemble 78 | TRCCall *decoded = [TRCCall new]; 79 | decoded.method = method; 80 | // TODO: rename millis -> startMs 81 | decoded.millis = [startMs unsignedIntegerValue]; 82 | decoded.arguments = [arguments copy]; 83 | decoded.returnValue = returnValue; 84 | return decoded; 85 | } 86 | 87 | - (NSObject *)jsonObject { 88 | NSMutableDictionary *json = [NSMutableDictionary new]; 89 | json[@"trace_object"] = @"call"; 90 | json[@"method"] = self.method; 91 | NSMutableArray *args = [NSMutableArray new]; 92 | for (TRCValue *arg in self.arguments) { 93 | [args addObject:[arg jsonObject]]; 94 | } 95 | json[@"arguments"] = [args copy]; 96 | json[@"start_ms"] = @(self.millis); 97 | json[@"return_value"] = [self.returnValue jsonObject]; 98 | return [json copy]; 99 | } 100 | 101 | + (TRCValue *)buildReturnValue:(NSInvocation *)inv { 102 | NSMethodSignature *sig = inv.methodSignature; 103 | NSString *encodingString = [NSString stringWithUTF8String:sig.methodReturnType]; 104 | TRCType returnType = [TRCValue typeWithEncoding:encodingString]; 105 | 106 | __unsafe_unretained id returnValue; 107 | switch (returnType) { 108 | case TRCTypeChar: { 109 | char value; 110 | [inv getReturnValue:&value]; 111 | returnValue = @(value); 112 | } break; 113 | case TRCTypeInt: { 114 | int value; 115 | [inv getReturnValue:&value]; 116 | returnValue = @(value); 117 | } break; 118 | case TRCTypeShort: { 119 | short value; 120 | [inv getReturnValue:&value]; 121 | returnValue = @(value); 122 | } break; 123 | case TRCTypeLong: { 124 | long value; 125 | [inv getReturnValue:&value]; 126 | returnValue = @(value); 127 | } break; 128 | case TRCTypeLongLong: { 129 | long long value; 130 | [inv getReturnValue:&value]; 131 | returnValue = @(value); 132 | } break; 133 | case TRCTypeUnsignedChar: { 134 | unsigned char value; 135 | [inv getReturnValue:&value]; 136 | returnValue = @(value); 137 | } break; 138 | case TRCTypeUnsignedInt: { 139 | unsigned int value; 140 | [inv getReturnValue:&value]; 141 | returnValue = @(value); 142 | } break; 143 | case TRCTypeUnsignedShort: { 144 | unsigned short value; 145 | [inv getReturnValue:&value]; 146 | returnValue = @(value); 147 | } break; 148 | case TRCTypeUnsignedLong: { 149 | unsigned long value; 150 | [inv getReturnValue:&value]; 151 | returnValue = @(value); 152 | } break; 153 | case TRCTypeUnsignedLongLong: { 154 | unsigned long long value; 155 | [inv getReturnValue:&value]; 156 | returnValue = @(value); 157 | } break; 158 | case TRCTypeFloat: { 159 | float value; 160 | [inv getReturnValue:&value]; 161 | returnValue = @(value); 162 | } break; 163 | case TRCTypeDouble: { 164 | double value; 165 | [inv getReturnValue:&value]; 166 | returnValue = @(value); 167 | } break; 168 | case TRCTypeBool: { 169 | BOOL value; 170 | [inv getReturnValue:&value]; 171 | returnValue = @(value); 172 | } break; 173 | case TRCTypeCharacterString: { 174 | const char *value; 175 | [inv getReturnValue:&value]; 176 | returnValue = [NSString stringWithUTF8String:value]; 177 | } break; 178 | case TRCTypeCGPoint: { 179 | CGPoint value; 180 | [inv getReturnValue:&value]; 181 | returnValue = [NSValue valueWithCGPoint:value]; 182 | } break; 183 | case TRCTypeCGSize: { 184 | CGSize value; 185 | [inv getReturnValue:&value]; 186 | returnValue = [NSValue valueWithCGSize:value]; 187 | } break; 188 | case TRCTypeCGRect: { 189 | CGRect value; 190 | [inv getReturnValue:&value]; 191 | returnValue = [NSValue valueWithCGRect:value]; 192 | } break; 193 | case TRCTypeUIEdgeInsets: { 194 | UIEdgeInsets value; 195 | [inv getReturnValue:&value]; 196 | returnValue = [NSValue valueWithUIEdgeInsets:value]; 197 | } break; 198 | case TRCTypeSEL: { 199 | SEL sel; 200 | [inv getReturnValue:&sel]; 201 | returnValue = [NSValue valueWithPointer:sel]; 202 | } break; 203 | case TRCTypeIMP: { 204 | IMP imp; 205 | [inv getReturnValue:&imp]; 206 | returnValue = [NSValue valueWithPointer:imp]; 207 | } break; 208 | case TRCTypeObject: 209 | case TRCTypeClass: 210 | [inv getReturnValue:&returnValue]; 211 | break; 212 | default: break; 213 | } 214 | TRCValue *value = [[TRCValue alloc] initWithTypeEncoding:encodingString 215 | boxedArgument:returnValue ?: @(0)]; 216 | return value; 217 | } 218 | 219 | - (NSString *)description { 220 | NSArray *props = @[ 221 | // Object 222 | [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self], 223 | 224 | // Details (alphabetical) 225 | [NSString stringWithFormat:@"jsonObject = %@", [self jsonObject]], 226 | ]; 227 | return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]]; 228 | } 229 | 230 | @end 231 | 232 | NS_ASSUME_NONNULL_END 233 | -------------------------------------------------------------------------------- /Tracer/TRCErrors.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCErrors.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 3/16/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | NS_SWIFT_NAME(ErrorDomain) 14 | FOUNDATION_EXPORT NSString * const TRCErrorDomain; 15 | 16 | /** 17 | Use this enum to access userInfo keys for NSError objects under the 18 | TRCErrorDomain domain. 19 | */ 20 | NS_SWIFT_NAME(ErrorKey) 21 | typedef NSString *const TRCErrorKey NS_STRING_ENUM; 22 | 23 | /** 24 | The TraceCall producing the error. 25 | */ 26 | FOUNDATION_EXPORT TRCErrorKey const TRCErrorKeyCall; 27 | 28 | /** 29 | Possible error codes for NSError objects under the TRCErrorDomain domain. 30 | */ 31 | typedef NS_ERROR_ENUM(TRCErrorDomain, TRCError) { 32 | 33 | /** 34 | Recording failed for an unexpected reason. 35 | */ 36 | TRCErrorRecordingFailedUnexpectedError = 1000, 37 | /** 38 | Recording failed because the JSON representation of the trace is invalid. 39 | */ 40 | TRCErrorRecordingFailedInvalidTraceJson = 1001, 41 | /** 42 | Recording failed because serializing the JSON trace to a string failed. 43 | */ 44 | TRCErrorRecordingFailedTraceJsonSerializationError = 1002, 45 | /** 46 | Recording failed because no active trace was found matching the given 47 | input. 48 | */ 49 | TRCErrorRecordingFailedTraceNotFound = 1003, 50 | 51 | /** 52 | Playback failed for an unexpected reason. 53 | */ 54 | TRCErrorPlaybackFailedUnexpectedError = 2000, 55 | /** 56 | Playback failed because the object is unknown. 57 | TODO: docs on using a fixture provider 58 | */ 59 | TRCErrorPlaybackFailedUnknownObject = 2001, 60 | /** 61 | Playback failed because the object type is unsupported. 62 | */ 63 | TRCErrorPlaybackFailedUnsupportedType = 2002, 64 | /** 65 | Playback failed because the fixture provider return nil for a requested value. 66 | */ 67 | TRCErrorPlaybackFailedFixtureProviderReturnedNil = 2003, 68 | /** 69 | Playback failed because the trace is nil. 70 | */ 71 | TRCErrorPlaybackFailedNilTrace = 2004, 72 | 73 | } NS_SWIFT_NAME(ErrorCode); 74 | 75 | NS_ASSUME_NONNULL_END 76 | -------------------------------------------------------------------------------- /Tracer/TRCErrors.m: -------------------------------------------------------------------------------- 1 | // 2 | // TRCErrors.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 3/16/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCErrors+Private.h" 10 | #import "TRCCall.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | NSString *const TRCErrorDomain = @"com.tracer"; 15 | NSString *const TRCErrorKeyCall = @"com.tracer:Call"; 16 | 17 | @implementation TRCErrors 18 | 19 | + (NSString *)stringFromErrorCode:(TRCError)code { 20 | switch (code) { 21 | case TRCErrorRecordingFailedInvalidTraceJson: 22 | return @"Recording failed because the JSON representation of the trace is invalid."; 23 | case TRCErrorRecordingFailedTraceJsonSerializationError: 24 | return @"Recording failed because serializing the JSON trace to a string failed."; 25 | case TRCErrorRecordingFailedUnexpectedError: 26 | return @"Recording failed for an unexpected reason."; 27 | case TRCErrorRecordingFailedTraceNotFound: 28 | return @"Recording failed because no active trace was found matching the given input."; 29 | case TRCErrorPlaybackFailedUnknownObject: 30 | return @"Playback failed because the object is unknown."; 31 | case TRCErrorPlaybackFailedUnsupportedType: 32 | return @"Playback failed because the object type is unsupported."; 33 | case TRCErrorPlaybackFailedFixtureProviderReturnedNil: 34 | return @"Playback failed because the fixture provider return nil for a requested value."; 35 | case TRCErrorPlaybackFailedUnexpectedError: 36 | return @"Playback failed for an unexpected reason."; 37 | case TRCErrorPlaybackFailedNilTrace: 38 | return @"Playback failed because the trace is nil."; 39 | } 40 | } 41 | 42 | + (NSError *)buildError:(TRCError)errorCode { 43 | NSString *message = [self stringFromErrorCode:errorCode]; 44 | NSDictionary *userInfo = @{ 45 | NSLocalizedDescriptionKey: message, 46 | }; 47 | NSError *error = [NSError errorWithDomain:TRCErrorDomain 48 | code:errorCode 49 | userInfo:userInfo]; 50 | return error; 51 | } 52 | 53 | + (NSError *)buildError:(TRCError)errorCode call:(TRCCall *)call message:(NSString *)message { 54 | NSDictionary *userInfo = @{ 55 | NSLocalizedDescriptionKey: message, 56 | TRCErrorKeyCall: call.jsonObject, 57 | }; 58 | NSError *error = [NSError errorWithDomain:TRCErrorDomain 59 | code:errorCode 60 | userInfo:userInfo]; 61 | return error; 62 | } 63 | 64 | @end 65 | 66 | NS_ASSUME_NONNULL_END 67 | -------------------------------------------------------------------------------- /Tracer/TRCFixtureProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCFixtureProvider.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 4/17/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | Fixture provider for a trace player. 15 | */ 16 | NS_SWIFT_NAME(FixtureProvider) 17 | @protocol TRCFixtureProvider 18 | 19 | /** 20 | Synchronously return a fixture for the given value. 21 | Returning nil causes playback to fail. 22 | */ 23 | - (nullable id)player:(TRCPlayer *)player didRequestFixtureForValue:(TRCValue *)value; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /Tracer/TRCJsonDecodable.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCJsonDecodable.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 4/25/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | Objects conforming to this protocol can be instantiated by decoding a JSON 15 | dictionary. 16 | */ 17 | NS_SWIFT_NAME(JsonDecodable) 18 | @protocol TRCJsonDecodable 19 | 20 | /** 21 | Parses a JSON dictionary into an instance of the class. Returns nil if the 22 | object could not be decoded. 23 | */ 24 | + (nullable instancetype)decodedObjectFromJson:(nullable NSDictionary *)json; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /Tracer/TRCJsonEncodable.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCJsonEncodable.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | Objects conforming to this protocol can be convert themself into a JSON 15 | object (NSDictionary or NSArray), suitable for use with `NSJSONSerialization`. 16 | */ 17 | NS_SWIFT_NAME(JsonEncodable) 18 | @protocol TRCJsonEncodable 19 | 20 | /** 21 | Converts the receiver into a JSON object (NSDictionary or NSArray), suitable 22 | for use with `NSJSONSerialization`. 23 | */ 24 | - (NSDictionary *)jsonObject; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /Tracer/TRCPlayer.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCPlayer.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TRCBlocks.h" 12 | 13 | @class TRCTrace; 14 | 15 | @protocol TRCFixtureProvider; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | /** 20 | Trace player 21 | */ 22 | NS_SWIFT_NAME(TracePlayer) 23 | @interface TRCPlayer : NSObject 24 | 25 | /** 26 | Plays a trace by performing recorded invocations on the main queue. 27 | If playback fails, completes with an error. 28 | If playback succeeds, completes with nil. 29 | */ 30 | - (void)playTrace:(TRCTrace *)trace 31 | onTarget:(id)target 32 | completion:(TRCErrorCompletionBlock)completion; 33 | 34 | /** 35 | Plays a trace by performing recorded invocations on the main queue. 36 | If an unknown object is encountered in the recorded trace, the player asks 37 | the fixture provider for a fixture. 38 | If playback fails, completes with an error. 39 | If playback succeeds, completes with nil. 40 | */ 41 | - (void)playTrace:(TRCTrace *)trace 42 | onTarget:(id)target 43 | withFixtureProvider:(id)fixtureProvider 44 | completion:(TRCErrorCompletionBlock)completion; 45 | 46 | @end 47 | 48 | NS_ASSUME_NONNULL_END 49 | -------------------------------------------------------------------------------- /Tracer/TRCPlayer.m: -------------------------------------------------------------------------------- 1 | // 2 | // TRCPlayer.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TRCPlayer.h" 12 | 13 | #import "TRCValue+Private.h" 14 | #import "TRCTrace+Private.h" 15 | #import "TRCCall+Private.h" 16 | #import "TRCDispatchFunctions.h" 17 | #import "TRCNotNil.h" 18 | #import "TRCErrors+Private.h" 19 | #import "TRCFixtureProvider.h" 20 | 21 | NS_ASSUME_NONNULL_BEGIN 22 | 23 | @interface TRCPlayer () 24 | 25 | @property (nonatomic, strong, readwrite) NSMutableDictionary *traceIdToObjectArgs; 26 | 27 | @end 28 | 29 | @implementation TRCPlayer 30 | 31 | - (instancetype)init { 32 | self = [super init]; 33 | if (self) { 34 | _traceIdToObjectArgs = [NSMutableDictionary new]; 35 | } 36 | return self; 37 | } 38 | 39 | - (void)playTrace:(TRCTrace *)trace 40 | onTarget:(id)target 41 | completion:(TRCErrorCompletionBlock)completion { 42 | [self _playTrace:trace 43 | onTarget:target 44 | withFixtureProvider:nil 45 | completion:completion]; 46 | } 47 | 48 | - (void)playTrace:(TRCTrace *)trace 49 | onTarget:(id)target 50 | withFixtureProvider:(id)fixtureProvider 51 | completion:(TRCErrorCompletionBlock)completion { 52 | [self _playTrace:trace 53 | onTarget:target 54 | withFixtureProvider:fixtureProvider 55 | completion:completion]; 56 | } 57 | 58 | - (void)_playTrace:(TRCTrace *)trace 59 | onTarget:(id)target 60 | withFixtureProvider:(nullable id)fixtureProvider 61 | completion:(TRCErrorCompletionBlock)completion { 62 | // TODO: validate target against trace 63 | if (trace == nil) { 64 | NSError *error = [TRCErrors buildError:TRCErrorPlaybackFailedNilTrace]; 65 | completion(error); 66 | return; 67 | } 68 | 69 | NSString *traceId = [trace internalId]; 70 | 71 | void (^cleanup)(void) = ^void() { 72 | // clear retained objects 73 | if (traceId != nil) { 74 | [self.traceIdToObjectArgs removeObjectForKey:traceId]; 75 | } 76 | }; 77 | 78 | NSTimeInterval longestDelay = 0; 79 | BOOL stopPlaying = NO; 80 | NSError *stopPlayingError = nil; 81 | for (TRCCall *call in trace.calls) { 82 | if (stopPlaying) { 83 | break; 84 | } 85 | 86 | SEL aSelector = NSSelectorFromString(call.method); 87 | NSMethodSignature *signature = [target methodSignatureForSelector:aSelector]; 88 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; 89 | [invocation setTarget:target]; 90 | [invocation setSelector:aSelector]; 91 | 92 | NSTimeInterval delay = ((double)call.millis)/1000.0; 93 | longestDelay = MAX(longestDelay, delay); 94 | 95 | for (NSUInteger i = 0; i < [call.arguments count]; i++) { 96 | TRCValue *arg = call.arguments[i]; 97 | // indices 0 and 1 indicate the hidden arguments self and _cmd 98 | NSUInteger index = i + 2; 99 | NSValue *__nullable objectValue = (NSValue *)arg.objectValue; 100 | TRCObjectType objectType = arg.objectType; 101 | TRCType type = arg.type; 102 | switch (type) { 103 | case TRCTypeObject: { 104 | id invocationArgument = nil; 105 | switch (objectType) { 106 | case TRCObjectTypeNotAnObject: { 107 | NSString *message = [NSString stringWithFormat:@"Invalid argument in trace: %@", arg.jsonObject]; 108 | stopPlayingError = [TRCErrors buildError:TRCErrorPlaybackFailedUnexpectedError 109 | call:call 110 | message:message]; 111 | stopPlaying = YES; 112 | } break; 113 | case TRCObjectTypeUnknownObject: 114 | case TRCObjectTypeUnknownArray: 115 | case TRCObjectTypeUnknownDictionary: 116 | { 117 | if (fixtureProvider != nil) { 118 | __nullable id fixture = [fixtureProvider player:self didRequestFixtureForValue:arg]; 119 | if (fixture == nil) { 120 | stopPlayingError = [TRCErrors buildError:TRCErrorPlaybackFailedFixtureProviderReturnedNil]; 121 | stopPlaying = YES; 122 | } 123 | else { 124 | invocationArgument = fixture; 125 | } 126 | } 127 | else { 128 | NSString *message = [NSString stringWithFormat:@"Can't play argument containing unknown object: %@", arg.objectClass]; 129 | stopPlayingError = [TRCErrors buildError:TRCErrorPlaybackFailedUnknownObject 130 | call:call 131 | message:message]; 132 | stopPlaying = YES; 133 | } 134 | } break; 135 | case TRCObjectTypeJsonObject: break; 136 | } 137 | // retain object arguments 138 | NSString *argId = [NSString stringWithFormat:@"%@_%lu", [call internalId], i]; 139 | NSMutableDictionary *argIdToArg = self.traceIdToObjectArgs[traceId]; 140 | if (argIdToArg == nil) { 141 | argIdToArg = [NSMutableDictionary new]; 142 | } 143 | if (invocationArgument == nil) { 144 | invocationArgument = (NSObject *)objectValue; 145 | } 146 | argIdToArg[argId] = invocationArgument; 147 | self.traceIdToObjectArgs[traceId] = argIdToArg; 148 | [invocation setArgument:&invocationArgument atIndex:index]; 149 | } break; 150 | case TRCTypeInt: { 151 | int value; 152 | [objectValue getValue:&value]; 153 | [invocation setArgument:&value atIndex:index]; 154 | } break; 155 | case TRCTypeUnsignedInt: { 156 | unsigned int value; 157 | [objectValue getValue:&value]; 158 | [invocation setArgument:&value atIndex:index]; 159 | } break; 160 | case TRCTypeFloat: { 161 | float value; 162 | [objectValue getValue:&value]; 163 | [invocation setArgument:&value atIndex:index]; 164 | } break; 165 | case TRCTypeBool: { 166 | BOOL value; 167 | [objectValue getValue:&value]; 168 | [invocation setArgument:&value atIndex:index]; 169 | } break; 170 | case TRCTypeDouble: { 171 | double value; 172 | [objectValue getValue:&value]; 173 | [invocation setArgument:&value atIndex:index]; 174 | } break; 175 | // TODO: test playback of more primitive types 176 | case TRCTypeCharacterString: { 177 | const char *value; 178 | [objectValue getValue:&value]; 179 | [invocation setArgument:&value atIndex:index]; 180 | } break; 181 | case TRCTypeClass: { 182 | Class value; 183 | [objectValue getValue:&value]; 184 | [invocation setArgument:&value atIndex:index]; 185 | } break; 186 | case TRCTypeShort: { 187 | short value; 188 | [objectValue getValue:&value]; 189 | [invocation setArgument:&value atIndex:index]; 190 | } break; 191 | case TRCTypeUnsignedShort: { 192 | unsigned short value; 193 | [objectValue getValue:&value]; 194 | [invocation setArgument:&value atIndex:index]; 195 | } break; 196 | case TRCTypeLong: { 197 | long value; 198 | [objectValue getValue:&value]; 199 | [invocation setArgument:&value atIndex:index]; 200 | } break; 201 | case TRCTypeUnsignedLong: { 202 | unsigned long value; 203 | [objectValue getValue:&value]; 204 | [invocation setArgument:&value atIndex:index]; 205 | } break; 206 | case TRCTypeLongLong: { 207 | long long value; 208 | [objectValue getValue:&value]; 209 | [invocation setArgument:&value atIndex:index]; 210 | } break; 211 | case TRCTypeUnsignedLongLong: { 212 | unsigned long long value; 213 | [objectValue getValue:&value]; 214 | [invocation setArgument:&value atIndex:index]; 215 | } break; 216 | case TRCTypeChar: { 217 | char value; 218 | [objectValue getValue:&value]; 219 | [invocation setArgument:&value atIndex:index]; 220 | } break; 221 | case TRCTypeUnsignedChar: { 222 | unsigned char value; 223 | [objectValue getValue:&value]; 224 | [invocation setArgument:&value atIndex:index]; 225 | } break; 226 | case TRCTypeSEL: { 227 | SEL value; 228 | [objectValue getValue:&value]; 229 | [invocation setArgument:&value atIndex:index]; 230 | } break; 231 | case TRCTypeVoid: { 232 | 233 | } break; 234 | case TRCTypeIMP: 235 | case TRCTypeCGRect: 236 | case TRCTypeCGSize: 237 | case TRCTypeCGPoint: 238 | case TRCTypeUIEdgeInsets: 239 | case TRCTypeUnknown: { 240 | NSString *typeString = [TRCValue stringFromType:type]; 241 | NSString *message = [NSString stringWithFormat:@"Can't play argument containing unsupported type: %@", typeString]; 242 | stopPlayingError = [TRCErrors buildError:TRCErrorPlaybackFailedUnsupportedType 243 | call:call 244 | message:message]; 245 | stopPlaying = YES; 246 | } 247 | } 248 | } 249 | 250 | if (!stopPlaying) { 251 | trcDispatchToMainAfter(delay, ^{ 252 | [invocation invoke]; 253 | }); 254 | } 255 | } 256 | trcDispatchToMainAfter(longestDelay, ^{ 257 | completion(stopPlayingError); 258 | cleanup(); 259 | }); 260 | } 261 | 262 | @end 263 | 264 | NS_ASSUME_NONNULL_END 265 | -------------------------------------------------------------------------------- /Tracer/TRCRecorder.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCRecorder.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TRCBlocks.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /** 16 | Trace recorder 17 | */ 18 | NS_SWIFT_NAME(TraceRecorder) 19 | @interface TRCRecorder : NSObject 20 | 21 | /** 22 | Returns the shared (singleton) Recorder instance. 23 | */ 24 | @property (class, nonatomic, readonly) TRCRecorder *shared; 25 | 26 | /** 27 | Defaults to false. Tracer is experimental, and still work-in-progress. 28 | */ 29 | @property (nonatomic, assign) BOOL debugModeEnabled; 30 | 31 | /** 32 | Starts recording a protocol on a source. 33 | */ 34 | - (void)startRecording:(id)source protocol:(Protocol *)protocol; 35 | 36 | /** 37 | Stops recording a protocol on a source. 38 | 39 | Note that optional protocol methods aren't currently supported, and will not 40 | be recorded. 41 | 42 | If recording fails, completes with an error. 43 | If recording succeeds, completes with nil. 44 | */ 45 | - (void)stopRecording:(id)source 46 | protocol:(Protocol *)protocol 47 | completion:(TRCTraceCompletionBlock)completion; 48 | 49 | @end 50 | 51 | NS_ASSUME_NONNULL_END 52 | -------------------------------------------------------------------------------- /Tracer/TRCRecorder.m: -------------------------------------------------------------------------------- 1 | // 2 | // TRCRecorder.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCRecorder.h" 10 | 11 | #import 12 | 13 | #import "TRCValue+Private.h" 14 | #import "TRCAspects.h" 15 | #import "TRCCall+Private.h" 16 | #import "TRCTrace+Private.h" 17 | #import "NSDate+Tracer.h" 18 | #import "TRCErrors+Private.h" 19 | 20 | NS_ASSUME_NONNULL_BEGIN 21 | 22 | @interface TRCRecorder () 23 | 24 | @property (atomic, strong, readwrite) dispatch_queue_t tracesQueue; 25 | @property (atomic, strong, readonly) NSMutableDictionary*keyToTrace; 26 | 27 | @end 28 | 29 | @implementation TRCRecorder 30 | 31 | static TRCRecorder * __nullable _shared = nil; 32 | 33 | + (instancetype)shared { 34 | static dispatch_once_t onceToken; 35 | dispatch_once(&onceToken, ^{ 36 | _shared = [TRCRecorder new]; 37 | }); 38 | return _shared; 39 | } 40 | 41 | - (instancetype)init { 42 | self = [super init]; 43 | if (self) { 44 | _tracesQueue = dispatch_queue_create("com.tracer.tracerecorder.traces", DISPATCH_QUEUE_SERIAL); 45 | _keyToTrace = [NSMutableDictionary new]; 46 | _debugModeEnabled = NO; 47 | } 48 | return self; 49 | } 50 | 51 | - (NSString *)buildKeyWithSource:(id)instance protocol:(Protocol *)protocol { 52 | return [@[ 53 | NSStringFromClass([instance class]), 54 | NSStringFromProtocol(protocol), 55 | ] componentsJoinedByString:@"."]; 56 | } 57 | 58 | - (void)startRecording:(id)source protocol:(Protocol *)protocol { 59 | NSDate *start = [NSDate date]; 60 | 61 | NSString *key = [self buildKeyWithSource:source protocol:protocol]; 62 | 63 | // list methods 64 | BOOL showRequiredMethods = YES; 65 | BOOL showInstanceMethods = YES; 66 | unsigned int methodCount = 0; 67 | struct objc_method_description *methodList = protocol_copyMethodDescriptionList(protocol, showRequiredMethods, showInstanceMethods, &methodCount); 68 | 69 | // get selectors 70 | NSMutableArray*methodSelectors = [NSMutableArray new]; 71 | NSMutableArray*methodSigs = [NSMutableArray new]; 72 | for (NSUInteger i = 0; i < methodCount; i++) { 73 | struct objc_method_description methodDescription = methodList[i]; 74 | NSValue *sel = [NSValue valueWithPointer:methodDescription.name]; 75 | [methodSelectors addObject:sel]; 76 | NSMethodSignature *sig = [NSMethodSignature signatureWithObjCTypes:methodDescription.types]; 77 | [methodSigs addObject:sig]; 78 | } 79 | 80 | if (self.debugModeEnabled) { 81 | NSLog(@"TRACER DEBUG: hooking %lu methods", (unsigned long)methodCount); 82 | } 83 | 84 | // hook selectors to record calls 85 | for (NSUInteger i = 0; i < [methodSelectors count]; i++) { 86 | NSValue *selValue = methodSelectors[i]; 87 | NSMethodSignature *sig = methodSigs[i]; 88 | 89 | // get types 90 | // TODO: refactor to build types inside TRCCall from the NSInvocation 91 | NSMutableArray *typeEncodings = [NSMutableArray new]; 92 | for (NSUInteger j = 0; j < sig.numberOfArguments; j++) { 93 | NSString *ts = [NSString stringWithUTF8String:[sig getArgumentTypeAtIndex:j]]; 94 | [typeEncodings addObject:ts]; 95 | } 96 | // indices 0 and 1 indicate the hidden arguments self and _cmd 97 | if ([typeEncodings count] >= 2) { 98 | [typeEncodings removeObjectAtIndex:0]; 99 | [typeEncodings removeObjectAtIndex:0]; 100 | } 101 | 102 | SEL sel = (SEL)selValue.pointerValue; 103 | Class klass = [source class]; 104 | NSLog(@"TRACER DEBUG: hooking selector %@", NSStringFromSelector(sel)); 105 | [klass trc_aspect_hookSelector:sel withOptions:TRCAspectPositionAfter usingBlock:^(id info){ 106 | NSUInteger ms = [[NSDate date] trc_millisSinceDate:start]; 107 | 108 | NSMutableArray*args = [NSMutableArray new]; 109 | for (NSUInteger j = 0; j < [typeEncodings count]; j++) { 110 | NSString *encoding = typeEncodings[j]; 111 | id boxedArg = info.arguments[j]; 112 | TRCValue *argument = [[TRCValue alloc] initWithTypeEncoding:encoding 113 | boxedArgument:boxedArg]; 114 | [args addObject:argument]; 115 | } 116 | 117 | TRCCall *call = [[TRCCall alloc] initWithSelector:sel 118 | invocation:info.originalInvocation 119 | arguments:[args copy] 120 | millis:ms]; 121 | NSLog(@"TRACER DEBUG: recording call %@", call); 122 | dispatch_async(self.tracesQueue, ^{ 123 | TRCTrace *trace = self.keyToTrace[key]; 124 | if (trace == nil) { 125 | trace = [[TRCTrace alloc] initWithProtocol:protocol]; 126 | } 127 | [trace addCall:call]; 128 | self.keyToTrace[key] = trace; 129 | }); 130 | } error:nil]; 131 | } 132 | 133 | free(methodList); 134 | } 135 | 136 | - (void)stopRecording:(id)source 137 | protocol:(Protocol *)protocol 138 | completion:(TRCTraceCompletionBlock)completion { 139 | NSString *key = [self buildKeyWithSource:source protocol:protocol]; 140 | dispatch_async(self.tracesQueue, ^{ 141 | // load trace 142 | TRCTrace *trace; 143 | if ([[self.keyToTrace allKeys] count] == 1) { 144 | NSString *firstKey = [[self.keyToTrace allKeys] firstObject]; 145 | trace = self.keyToTrace[firstKey]; 146 | } 147 | else { 148 | trace = self.keyToTrace[key]; 149 | } 150 | 151 | if (trace != nil) { 152 | if (![NSJSONSerialization isValidJSONObject:trace.jsonObject]) { 153 | NSError *invalidJsonError = [TRCErrors buildError:TRCErrorRecordingFailedInvalidTraceJson]; 154 | completion(nil, invalidJsonError); 155 | } 156 | else { 157 | NSError *jsonWritingError; 158 | NSData *jsonData = [NSJSONSerialization dataWithJSONObject:trace.jsonObject 159 | options:(NSJSONWritingOptions)NSJSONWritingPrettyPrinted 160 | error:&jsonWritingError]; 161 | if (jsonWritingError != nil) { 162 | completion(nil, jsonWritingError); 163 | } 164 | else { 165 | NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; 166 | if (jsonString == nil) { 167 | NSError *serializationError = [TRCErrors buildError:TRCErrorRecordingFailedTraceJsonSerializationError]; 168 | completion(nil, serializationError); 169 | } 170 | else { 171 | NSString *header = @"-----BEGIN TRACE JSON-----"; 172 | NSString *footer = @"-----END TRACE JSON-----"; 173 | NSArray *lines = @[ 174 | header, 175 | jsonString, 176 | footer, 177 | ]; 178 | NSString *message = [lines componentsJoinedByString:@"\n"]; 179 | NSLog(@"%@", message); 180 | } 181 | completion(trace, nil); 182 | } 183 | } 184 | } 185 | else { 186 | NSLog(@"TRACER DEBUG: couldn't find trace %@", self.keyToTrace); 187 | NSError *error = [TRCErrors buildError:TRCErrorRecordingFailedUnexpectedError]; 188 | completion(nil, error); 189 | } 190 | [self.keyToTrace removeObjectForKey:key]; 191 | }); 192 | } 193 | 194 | @end 195 | 196 | NS_ASSUME_NONNULL_END 197 | -------------------------------------------------------------------------------- /Tracer/TRCTrace.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCTrace.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TRCJsonDecodable.h" 12 | #import "TRCJsonEncodable.h" 13 | 14 | @class TRCCall; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /** 19 | A trace. A trace is a timeseries of calls. 20 | */ 21 | NS_SWIFT_NAME(Trace) 22 | @interface TRCTrace : NSObject 23 | 24 | /** 25 | The recorded calls. 26 | */ 27 | @property (atomic, readonly) NSMutableArray*calls; 28 | 29 | /** 30 | The recorded protocol. 31 | */ 32 | @property (atomic, readonly) NSString *protocol; 33 | 34 | /** 35 | Loads a trace from a file in the given bundle. 36 | */ 37 | + (nullable instancetype)loadFromJsonFile:(NSString *)filename 38 | bundle:(NSBundle *)bundle; 39 | 40 | - (instancetype)init NS_UNAVAILABLE; 41 | 42 | @end 43 | 44 | NS_ASSUME_NONNULL_END 45 | -------------------------------------------------------------------------------- /Tracer/TRCTrace.m: -------------------------------------------------------------------------------- 1 | // 2 | // TRCTraceRecording.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCTrace+Private.h" 10 | #import "TRCCall.h" 11 | #import "NSDate+Tracer.h" 12 | #import "NSDictionary+Tracer.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface TRCTrace () 17 | 18 | @property (atomic, strong, readwrite) NSDate *start; 19 | @property (atomic, strong, readwrite) NSString *protocol; 20 | @property (atomic, strong, readwrite) NSMutableArray*calls; 21 | 22 | @end 23 | 24 | @implementation TRCTrace 25 | 26 | + (nullable instancetype)loadFromJsonFile:(NSString *)filename bundle:(nonnull NSBundle *)bundle { 27 | NSData *data = [self dataFromJSONFile:filename bundle:bundle]; 28 | NSDictionary *json; 29 | if (data != nil) { 30 | json = [NSJSONSerialization JSONObjectWithData:data options:(NSJSONReadingOptions)kNilOptions error:nil]; 31 | } 32 | if (json != nil) { 33 | return [self decodedObjectFromJson:json]; 34 | } 35 | return nil; 36 | } 37 | 38 | + (nullable instancetype)decodedObjectFromJson:(nullable NSDictionary *)json { 39 | // precheck 40 | if (!json) { 41 | return nil; 42 | } 43 | NSDictionary *dict = [json trc_dictionaryByRemovingNulls]; 44 | NSString *object = [dict trc_stringForKey:@"trace_object"]; 45 | if (![object isEqualToString:@"trace"]) { 46 | return nil; 47 | } 48 | 49 | // props 50 | NSString *protocol = [dict trc_stringForKey:@"protocol"]; 51 | NSNumber *startMs = [dict trc_numberForKey:@"start_ms"]; 52 | NSArray *rawCalls = [dict trc_arrayForKey:@"calls"]; 53 | if (!protocol || !startMs || !rawCalls) { 54 | return nil; 55 | } 56 | 57 | NSMutableArray *calls = [NSMutableArray new]; 58 | for (NSDictionary *rawCall in rawCalls) { 59 | TRCCall *call = [TRCCall decodedObjectFromJson:rawCall]; 60 | if (call != nil) { 61 | [calls addObject:call]; 62 | } 63 | } 64 | 65 | // assemble 66 | TRCTrace *decoded = [TRCTrace new]; 67 | decoded.protocol = protocol; 68 | decoded.start = [NSDate dateWithTimeIntervalSince1970:[startMs unsignedIntegerValue]]; 69 | decoded.calls = [calls copy]; 70 | return decoded; 71 | } 72 | 73 | - (instancetype)initWithProtocol:(Protocol *)protocol { 74 | self = [super init]; 75 | if (self) { 76 | _start = [NSDate date]; 77 | _protocol = NSStringFromProtocol(protocol); 78 | _calls = [NSMutableArray new]; 79 | } 80 | return self; 81 | } 82 | 83 | - (void)addCall:(TRCCall *)call { 84 | [self.calls addObject:call]; 85 | } 86 | 87 | - (NSUInteger)startMillis { 88 | return [self.start trc_millisSince1970]; 89 | } 90 | 91 | - (NSString *)internalId { 92 | return [NSString stringWithFormat:@"%@_%lu", self.protocol, [self startMillis]]; 93 | } 94 | 95 | - (NSObject *)jsonObject { 96 | NSMutableDictionary *json = [NSMutableDictionary new]; 97 | json[@"trace_object"] = @"trace"; 98 | json[@"protocol"] = self.protocol; 99 | json[@"start_ms"] = @([self startMillis]); 100 | NSMutableArray *calls = [NSMutableArray new]; 101 | for (TRCCall *call in self.calls) { 102 | [calls addObject:[call jsonObject]]; 103 | } 104 | json[@"calls"] = [calls copy]; 105 | return [json copy]; 106 | } 107 | 108 | + (nullable NSData *)dataFromJSONFile:(NSString *)name bundle:(NSBundle *)bundle { 109 | NSString *path = [bundle pathForResource:name ofType:@"json"]; 110 | if (!path) { 111 | return nil; 112 | } 113 | 114 | NSError *error = nil; 115 | NSString *jsonString = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error]; 116 | if (!jsonString) { 117 | return nil; 118 | } 119 | 120 | // Strip all lines that begin with `//` 121 | NSMutableArray *jsonLines = [[NSMutableArray alloc] init]; 122 | 123 | for (NSString *line in [jsonString componentsSeparatedByString:@"\n"]) { 124 | if (![line hasPrefix:@"//"]) { 125 | [jsonLines addObject:line]; 126 | } 127 | } 128 | 129 | return [[jsonLines componentsJoinedByString:@"\n"] dataUsingEncoding:NSUTF8StringEncoding]; 130 | } 131 | 132 | - (NSString *)description { 133 | NSArray *props = @[ 134 | // Object 135 | [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self], 136 | 137 | // Details (alphabetical) 138 | [NSString stringWithFormat:@"jsonObject = %@", [self jsonObject]], 139 | ]; 140 | return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]]; 141 | } 142 | 143 | @end 144 | 145 | NS_ASSUME_NONNULL_END 146 | -------------------------------------------------------------------------------- /Tracer/TRCType.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCType.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 3/3/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | typedef NS_ENUM(NSInteger, TRCType) { 12 | TRCTypeUnknown, 13 | TRCTypeChar, 14 | TRCTypeInt, 15 | TRCTypeShort, 16 | TRCTypeLong, 17 | TRCTypeLongLong, 18 | TRCTypeUnsignedChar, 19 | TRCTypeUnsignedInt, 20 | TRCTypeUnsignedShort, 21 | TRCTypeUnsignedLong, 22 | TRCTypeUnsignedLongLong, 23 | TRCTypeFloat, 24 | TRCTypeDouble, 25 | TRCTypeBool, 26 | TRCTypeVoid, 27 | TRCTypeCharacterString, 28 | TRCTypeCGPoint, 29 | TRCTypeCGSize, 30 | TRCTypeCGRect, 31 | TRCTypeUIEdgeInsets, 32 | TRCTypeObject, 33 | TRCTypeClass, 34 | TRCTypeSEL, 35 | TRCTypeIMP, 36 | }; 37 | 38 | typedef NS_ENUM(NSUInteger, TRCObjectType) { 39 | TRCObjectTypeNotAnObject, 40 | TRCObjectTypeJsonObject, 41 | TRCObjectTypeUnknownArray, 42 | TRCObjectTypeUnknownDictionary, 43 | TRCObjectTypeUnknownObject, 44 | }; 45 | -------------------------------------------------------------------------------- /Tracer/TRCValue.h: -------------------------------------------------------------------------------- 1 | // 2 | // TRCValue.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/25/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TRCJsonDecodable.h" 12 | #import "TRCJsonEncodable.h" 13 | #import "TRCType.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | /** 18 | A trace value. 19 | A call's arguments are represented as an array of TRCValues. 20 | */ 21 | NS_SWIFT_NAME(TraceValue) 22 | @interface TRCValue : NSObject 23 | 24 | /** 25 | The value's type. 26 | */ 27 | @property (atomic, readonly) TRCType type; 28 | 29 | /** 30 | The value's object type. 31 | */ 32 | @property (atomic, readonly) TRCObjectType objectType; 33 | 34 | /** 35 | The value's class. 36 | If the value has type Primitive, this is nil. 37 | */ 38 | @property (atomic, nullable, readonly) NSString *objectClass; 39 | 40 | /** 41 | The value's object value. 42 | If the value has type Primitive, this is the boxed value. 43 | If the value has type UnknownObject, UnknownArray, or UnknownDictionary, 44 | this is the String returned by the object's `description` selector. 45 | */ 46 | @property (atomic, nullable, readonly) id objectValue; 47 | 48 | - (instancetype)init NS_UNAVAILABLE; 49 | 50 | @end 51 | 52 | NS_ASSUME_NONNULL_END 53 | -------------------------------------------------------------------------------- /Tracer/TRCValue.m: -------------------------------------------------------------------------------- 1 | // 2 | // TRCValue.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/25/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCValue.h" 10 | 11 | #import 12 | 13 | #import "NSDictionary+Tracer.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface TRCValue () 18 | 19 | @property (atomic, assign, readwrite) TRCType type; 20 | @property (atomic, assign, readwrite) TRCObjectType objectType; 21 | @property (atomic, strong, nullable, readwrite) NSString *objectClass; 22 | @property (atomic, strong, nullable, readwrite) id objectValue; 23 | 24 | @end 25 | 26 | @implementation TRCValue 27 | 28 | static NSDictionary *_typeToString; 29 | static NSDictionary *_objectTypeToString; 30 | 31 | - (instancetype)initWithTypeEncoding:(NSString *)encoding 32 | boxedArgument:(id)boxedArgument { 33 | self = [super init]; 34 | if (self) { 35 | TRCType type = [[self class] typeWithEncoding:encoding]; 36 | _type = type; 37 | BOOL isObject = (type == TRCTypeObject); 38 | if (isObject) { 39 | NSString *classString = NSStringFromClass([boxedArgument class]); 40 | // NSString 41 | if ([classString containsString:@"NSCFConstantString"] || 42 | [classString containsString:@"NSCFString"]) { 43 | classString = @"NSString"; 44 | _objectType = TRCObjectTypeJsonObject; 45 | _objectValue = boxedArgument; 46 | } 47 | // NSNumber 48 | else if ([classString containsString:@"NSCFNumber"]) { 49 | classString = @"NSNumber"; 50 | _objectType = TRCObjectTypeJsonObject; 51 | _objectValue = boxedArgument; 52 | } 53 | // Boxed Bool 54 | else if ([classString containsString:@"NSCFBoolean"]) { 55 | classString = @"NSNumber"; 56 | _objectType = TRCObjectTypeJsonObject; 57 | _objectValue = boxedArgument; 58 | } 59 | // Valid JSON object 60 | else if ([NSJSONSerialization isValidJSONObject:boxedArgument]) { 61 | _objectType = TRCObjectTypeJsonObject; 62 | _objectValue = boxedArgument; 63 | } 64 | // NSDictionary, not valid JSON 65 | else if ([classString containsString:@"NSSingleEntryDictionary"] || 66 | [classString containsString:@"NSDictionary"]) { 67 | classString = @"NSDictionary"; 68 | _objectType = TRCObjectTypeUnknownDictionary; 69 | // TODO: we can do better than description 70 | _objectValue = [boxedArgument description]; 71 | } 72 | // NSArray, not valid JSON 73 | else if ([classString containsString:@"NSSingleObjectArray"] || 74 | [classString containsString:@"NSArray"]) { 75 | classString = @"NSArray"; 76 | _objectType = TRCObjectTypeUnknownArray; 77 | // TODO: we can do better than description 78 | _objectValue = [boxedArgument description]; 79 | } 80 | else { 81 | _objectType = TRCObjectTypeUnknownObject; 82 | // TODO: we can do better than description 83 | _objectValue = [boxedArgument description]; 84 | } 85 | _objectClass = classString; 86 | } 87 | else { 88 | _objectType = TRCObjectTypeNotAnObject; 89 | _objectValue = boxedArgument; 90 | } 91 | } 92 | return self; 93 | } 94 | 95 | + (nullable instancetype)decodedObjectFromJson:(nullable NSDictionary *)json { 96 | // precheck 97 | if (!json) { 98 | return nil; 99 | } 100 | NSDictionary *dict = [json trc_dictionaryByRemovingNulls]; 101 | NSString *object = [dict trc_stringForKey:@"trace_object"]; 102 | if (![object isEqualToString:@"value"]) { 103 | return nil; 104 | } 105 | 106 | // props 107 | NSString *rawType = [json trc_stringForKey:@"type"]; 108 | NSString *rawObjectType = [json trc_stringForKey:@"object_type"]; 109 | NSString *classString = [json trc_stringForKey:@"object_class"]; 110 | NSNumber *boxedType = [[self class] typeFromString:rawType]; 111 | NSNumber *boxedObjectType = [[self class] objectTypeFromString:rawObjectType]; 112 | id objectValue = [json objectForKey:@"object_value"]; 113 | if (!boxedType || !boxedObjectType) { 114 | return nil; 115 | } 116 | 117 | // assemble 118 | TRCValue *decoded = [TRCValue new]; 119 | decoded.type = [boxedType unsignedIntegerValue]; 120 | decoded.objectType = [boxedObjectType unsignedIntegerValue]; 121 | decoded.objectClass = classString; 122 | decoded.objectValue = objectValue; 123 | return decoded; 124 | } 125 | 126 | - (NSObject *)jsonObject { 127 | NSMutableDictionary *json = [NSMutableDictionary new]; 128 | json[@"trace_object"] = @"value"; 129 | json[@"type"] = [[self class] stringFromType:self.type]; 130 | json[@"object_type"] = [[self class] stringFromObjectType:self.objectType]; 131 | json[@"object_class"] = self.objectClass; 132 | json[@"object_value"] = self.objectValue; 133 | return [json copy]; 134 | } 135 | 136 | /** 137 | https://nshipster.com/type-encodings/ 138 | */ 139 | + (TRCType)typeWithEncoding:(NSString *)encodingString { 140 | const char *typeEncoding = [encodingString UTF8String]; 141 | if (strcmp(typeEncoding, @encode(char)) == 0) { 142 | return TRCTypeChar; 143 | } else if (strcmp(typeEncoding, @encode(int)) == 0) { 144 | return TRCTypeInt; 145 | } else if (strcmp(typeEncoding, @encode(short)) == 0) { 146 | return TRCTypeShort; 147 | } else if (strcmp(typeEncoding, @encode(long)) == 0) { 148 | return TRCTypeLong; 149 | } else if (strcmp(typeEncoding, @encode(long long)) == 0) { 150 | return TRCTypeLongLong; 151 | } else if (strcmp(typeEncoding, @encode(unsigned char)) == 0) { 152 | return TRCTypeUnsignedChar; 153 | } else if (strcmp(typeEncoding, @encode(unsigned int)) == 0) { 154 | return TRCTypeUnsignedInt; 155 | } else if (strcmp(typeEncoding, @encode(unsigned short)) == 0) { 156 | return TRCTypeUnsignedShort; 157 | } else if (strcmp(typeEncoding, @encode(unsigned long)) == 0) { 158 | return TRCTypeUnsignedLong; 159 | } else if (strcmp(typeEncoding, @encode(unsigned long long)) == 0) { 160 | return TRCTypeUnsignedLongLong; 161 | } else if (strcmp(typeEncoding, @encode(float)) == 0) { 162 | return TRCTypeFloat; 163 | } else if (strcmp(typeEncoding, @encode(double)) == 0) { 164 | return TRCTypeDouble; 165 | } else if (strcmp(typeEncoding, @encode(BOOL)) == 0) { 166 | return TRCTypeBool; 167 | } else if (strcmp(typeEncoding, @encode(void)) == 0) { 168 | return TRCTypeVoid; 169 | } else if (strcmp(typeEncoding, @encode(char *)) == 0) { 170 | return TRCTypeCharacterString; 171 | } else if (strcmp(typeEncoding, @encode(id)) == 0) { 172 | return TRCTypeObject; 173 | } else if (strcmp(typeEncoding, @encode(Class)) == 0) { 174 | return TRCTypeClass; 175 | } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) { 176 | return TRCTypeCGPoint; 177 | } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) { 178 | return TRCTypeCGSize; 179 | } else if (strcmp(typeEncoding, @encode(CGRect)) == 0) { 180 | return TRCTypeCGRect; 181 | } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) { 182 | return TRCTypeUIEdgeInsets; 183 | } else if (strcmp(typeEncoding, @encode(SEL)) == 0) { 184 | return TRCTypeSEL; 185 | } else if (strcmp(typeEncoding, @encode(IMP))) { 186 | return TRCTypeIMP; 187 | } else { 188 | return TRCTypeUnknown; 189 | } 190 | } 191 | 192 | + (NSDictionary*)typeToString { 193 | static dispatch_once_t onceToken; 194 | dispatch_once(&onceToken, ^{ 195 | _typeToString = @{ 196 | @(TRCTypeUnknown): @"unknown", 197 | @(TRCTypeChar): @"char", 198 | @(TRCTypeInt): @"int", 199 | @(TRCTypeShort): @"short", 200 | @(TRCTypeLong): @"long", 201 | @(TRCTypeLongLong): @"long_long", 202 | @(TRCTypeUnsignedChar): @"unsigned_char", 203 | @(TRCTypeUnsignedInt): @"unsigned_int", 204 | @(TRCTypeUnsignedShort): @"unsigned_short", 205 | @(TRCTypeUnsignedLong): @"unsigned_long", 206 | @(TRCTypeUnsignedLongLong): @"unsigned_long_long", 207 | @(TRCTypeFloat): @"float", 208 | @(TRCTypeDouble): @"double", 209 | @(TRCTypeBool): @"bool", 210 | @(TRCTypeVoid): @"void", 211 | @(TRCTypeCharacterString): @"character_string", 212 | @(TRCTypeCGPoint): @"cgpoint", 213 | @(TRCTypeCGSize): @"cgsize", 214 | @(TRCTypeCGRect): @"cgrect", 215 | @(TRCTypeUIEdgeInsets): @"uiedgeInsets", 216 | @(TRCTypeObject): @"object", 217 | @(TRCTypeClass): @"class", 218 | @(TRCTypeSEL): @"sel", 219 | @(TRCTypeIMP): @"imp", 220 | }; 221 | }); 222 | return _typeToString; 223 | } 224 | 225 | + (nullable NSString *)stringFromType:(TRCType)type { 226 | NSDictionary*mapping = [self typeToString]; 227 | return mapping[@(type)]; 228 | } 229 | 230 | + (nullable NSNumber *)typeFromString:(NSString *)string { 231 | NSDictionary*mapping = [self typeToString]; 232 | return [[mapping allKeysForObject:string] firstObject]; 233 | } 234 | 235 | + (NSDictionary*)objectTypeToString { 236 | static dispatch_once_t onceToken; 237 | dispatch_once(&onceToken, ^{ 238 | _objectTypeToString = @{ 239 | @(TRCObjectTypeNotAnObject): @"not_an_object", 240 | @(TRCObjectTypeUnknownObject): @"unknown_object", 241 | @(TRCObjectTypeJsonObject): @"json_object", 242 | @(TRCObjectTypeUnknownArray): @"unknown_array", 243 | @(TRCObjectTypeUnknownDictionary): @"unknown_dictionary", 244 | }; 245 | }); 246 | return _objectTypeToString; 247 | } 248 | 249 | + (nullable NSString *)stringFromObjectType:(TRCObjectType)type { 250 | NSDictionary*mapping = [self objectTypeToString]; 251 | return mapping[@(type)]; 252 | } 253 | 254 | + (nullable NSNumber *)objectTypeFromString:(NSString *)string { 255 | NSDictionary*mapping = [self objectTypeToString]; 256 | return [[mapping allKeysForObject:string] firstObject]; 257 | } 258 | 259 | - (NSString *)description { 260 | NSArray *props = @[ 261 | // Object 262 | [NSString stringWithFormat:@"%@: %p", NSStringFromClass([self class]), self], 263 | 264 | // Details (alphabetical) 265 | [NSString stringWithFormat:@"jsonObject = %@", [self jsonObject]], 266 | ]; 267 | return [NSString stringWithFormat:@"<%@>", [props componentsJoinedByString:@"; "]]; 268 | } 269 | 270 | 271 | 272 | @end 273 | 274 | NS_ASSUME_NONNULL_END 275 | -------------------------------------------------------------------------------- /Tracer/Tracer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tracer.h 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCRecorder.h" 10 | #import "TRCPlayer.h" 11 | #import "TRCCall.h" 12 | #import "TRCBlocks.h" 13 | #import "TRCJsonDecodable.h" 14 | #import "TRCJsonEncodable.h" 15 | #import "TRCTrace.h" 16 | #import "TRCType.h" 17 | #import "TRCValue.h" 18 | #import "TRCErrors.h" 19 | #import "TRCFixtureProvider.h" 20 | 21 | 22 | -------------------------------------------------------------------------------- /TracerTests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stripe/tracer-objc/03abf6d5bfb403114ac9fd80d6aa1f5b49662d49/TracerTests/.DS_Store -------------------------------------------------------------------------------- /TracerTests/ErrorTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorTests.m 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 3/16/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "TRCTestTarget.h" 12 | #import "TRCDispatchFunctions.h" 13 | 14 | @interface ErrorTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation ErrorTests 19 | 20 | - (void)testErrorUnknownObject { 21 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 22 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 23 | TRCRecorder *recorder = [TRCRecorder new]; 24 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 25 | 26 | NSArray *blocks = @[ 27 | (^{ 28 | [t ret_void__args_int:-100]; 29 | }), 30 | (^{ 31 | [t ret_void__args_object:[NSObject new]]; 32 | }), 33 | (^{ 34 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 35 | XCTAssertNotNil(trace); 36 | XCTAssertNil(recError); 37 | TRCPlayer *player = [TRCPlayer new]; 38 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 39 | XCTAssertNotNil(playError); 40 | XCTAssertEqual(playError.code, TRCErrorPlaybackFailedUnknownObject); 41 | [exp fulfill]; 42 | }]; 43 | }]; 44 | }), 45 | ]; 46 | 47 | for (NSUInteger i = 0; i < blocks.count; i++) { 48 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 49 | trcDispatchToMainAfter(delay, blocks[i]); 50 | } 51 | 52 | [self waitForExpectationsWithTimeout:10 handler:nil]; 53 | } 54 | 55 | - (void)testErrorUnsupportedType { 56 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 57 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 58 | TRCRecorder *recorder = [TRCRecorder new]; 59 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 60 | 61 | NSArray *blocks = @[ 62 | (^{ 63 | [t ret_void__args_int:-100]; 64 | }), 65 | (^{ 66 | [t ret_void__args_cgsize:CGSizeMake(0, 0)]; 67 | }), 68 | (^{ 69 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 70 | XCTAssertNil(trace); 71 | XCTAssertNotNil(recError); 72 | XCTAssertEqual(recError.code, TRCErrorRecordingFailedInvalidTraceJson); 73 | [exp fulfill]; 74 | }]; 75 | }), 76 | ]; 77 | 78 | for (NSUInteger i = 0; i < blocks.count; i++) { 79 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 80 | trcDispatchToMainAfter(delay, blocks[i]); 81 | } 82 | 83 | [self waitForExpectationsWithTimeout:10 handler:nil]; 84 | } 85 | 86 | @end 87 | -------------------------------------------------------------------------------- /TracerTests/FixtureProviderTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // FixtureProviderTests.m 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 4/17/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "TRCTestTarget.h" 12 | #import "TRCDispatchFunctions.h" 13 | 14 | @interface FixtureProviderTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation FixtureProviderTests 19 | 20 | - (void)testSingleFixture { 21 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 22 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 23 | TRCRecorder *recorder = [TRCRecorder new]; 24 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 25 | 26 | NSArray *blocks = @[ 27 | (^{ 28 | TRCCustomObject *o = [TRCCustomObject new]; 29 | o.value = @"one"; 30 | [t ret_void__args_custom_object:o]; 31 | }), 32 | (^{ 33 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 34 | XCTAssertNotNil(trace); 35 | XCTAssertNil(recError); 36 | TRCPlayer *player = [TRCPlayer new]; 37 | [player playTrace:trace 38 | onTarget:t 39 | withFixtureProvider:self 40 | completion:^(NSError * _Nullable playError) { 41 | XCTAssertNil(playError); 42 | [exp fulfill]; 43 | }]; 44 | }]; 45 | }), 46 | ]; 47 | 48 | for (NSUInteger i = 0; i < blocks.count; i++) { 49 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 50 | trcDispatchToMainAfter(delay, blocks[i]); 51 | } 52 | 53 | [self waitForExpectationsWithTimeout:10 handler:nil]; 54 | } 55 | 56 | - (void)testFixtureArray { 57 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 58 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 59 | TRCRecorder *recorder = [TRCRecorder new]; 60 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 61 | 62 | NSArray *blocks = @[ 63 | (^{ 64 | TRCCustomObject *one = [TRCCustomObject new]; 65 | one.value = @"one"; 66 | TRCCustomObject *two = [TRCCustomObject new]; 67 | two.value = @"two"; 68 | TRCCustomObject *three = [TRCCustomObject new]; 69 | three.value = @"three"; 70 | [t ret_void__args_custom_object_array:@[one, two, three]]; 71 | }), 72 | (^{ 73 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 74 | XCTAssertNotNil(trace); 75 | XCTAssertNil(recError); 76 | TRCPlayer *player = [TRCPlayer new]; 77 | [player playTrace:trace 78 | onTarget:t 79 | withFixtureProvider:self 80 | completion:^(NSError * _Nullable playError) { 81 | XCTAssertNil(playError); 82 | [exp fulfill]; 83 | }]; 84 | }]; 85 | }), 86 | ]; 87 | 88 | for (NSUInteger i = 0; i < blocks.count; i++) { 89 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 90 | trcDispatchToMainAfter(delay, blocks[i]); 91 | } 92 | 93 | [self waitForExpectationsWithTimeout:10 handler:nil]; 94 | } 95 | 96 | - (id)player:(TRCPlayer *)player didRequestFixtureForValue:(TRCValue *)value { 97 | NSString *customClassString = NSStringFromClass([TRCCustomObject class]); 98 | // single fixture 99 | if ([value.objectClass isEqualToString:customClassString]) { 100 | TRCCustomObject *fixture = [TRCCustomObject new]; 101 | fixture.value = @"one"; 102 | return fixture; 103 | } 104 | // fixture array 105 | else if (value.objectType == TRCObjectTypeUnknownArray && 106 | [((NSString *)value.objectValue) containsString:customClassString]) { 107 | TRCCustomObject *one = [TRCCustomObject new]; 108 | one.value = @"one"; 109 | TRCCustomObject *two = [TRCCustomObject new]; 110 | two.value = @"two"; 111 | TRCCustomObject *three = [TRCCustomObject new]; 112 | three.value = @"three"; 113 | return @[one, two, three]; 114 | } 115 | return nil; 116 | } 117 | 118 | @end 119 | -------------------------------------------------------------------------------- /TracerTests/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 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TracerTests/LoadFromFileTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LoadFromFileTests.m 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 4/17/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "TRCTestTarget.h" 12 | #import "TRCDispatchFunctions.h" 13 | 14 | @interface LoadFromFileTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation LoadFromFileTests 19 | 20 | - (void)testloadFromJsonFile { 21 | NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; 22 | TRCTrace *trace = [TRCTrace loadFromJsonFile:@"trace_bt_scan_connect" bundle:testBundle]; 23 | XCTAssertNotNil(trace); 24 | } 25 | 26 | - (void)xtestPlaybackFromFile { 27 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 28 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 29 | TRCPlayer *player = [TRCPlayer new]; 30 | NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; 31 | // Playing back floats (maybe all primitives) from a file fails. 32 | // I think this may be because Player needs to retain them? 33 | TRCTrace *trace = [TRCTrace loadFromJsonFile:@"saved_trace" bundle:testBundle]; 34 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 35 | XCTAssertNil(playError); 36 | [exp fulfill]; 37 | }]; 38 | 39 | [self waitForExpectationsWithTimeout:10 handler:nil]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /TracerTests/NSArray+TracerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+TracerTests.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 7/29/17. 6 | // Copyright © 2017 Stripe. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "NSArray+Tracer.h" 12 | 13 | @interface NSArray_TracerTests : XCTestCase 14 | 15 | @end 16 | 17 | @implementation NSArray_TracerTests 18 | 19 | - (void)test_safeSubarray { 20 | NSArray *test = @[]; 21 | XCTAssertNil([test trc_safeSubarrayWithRange:NSMakeRange(1, 1)]); 22 | 23 | test = @[@1, @2]; 24 | XCTAssertNil([test trc_safeSubarrayWithRange:NSMakeRange(0, 3)]); 25 | 26 | test = @[@1, @2, @3, @4]; 27 | NSArray *result = [test trc_safeSubarrayWithRange:NSMakeRange(1, 2)]; 28 | NSArray *expected = @[@2, @3]; 29 | XCTAssertEqualObjects(result, expected); 30 | } 31 | 32 | - (void)test_safeObjectAtIndex { 33 | NSArray *test = @[]; 34 | XCTAssertNil([test trc_safeObjectAtIndex:5]); 35 | test = @[@1, @2, @3]; 36 | XCTAssertNil([test trc_safeObjectAtIndex:5]); 37 | test = @[@1, @2, @3]; 38 | XCTAssertEqual([test trc_safeObjectAtIndex:1], @2); 39 | } 40 | 41 | - (void)test_arrayByRemovingNulls_removesNullsDeeply { 42 | NSArray *array = @[ 43 | @"id", 44 | [NSNull null], // null in root 45 | @{ 46 | @"user": @"user_123", 47 | @"country": [NSNull null], // null in dictionary 48 | @"nicknames": @[ 49 | @"john", 50 | @"johnny", 51 | [NSNull null], // null in array in dictionary 52 | ], 53 | @"profiles": @{ 54 | @"facebook": @"fb_123", 55 | @"twitter": [NSNull null], // null in dictionary in dictionary 56 | } 57 | }, 58 | @[ 59 | [NSNull null], // null in array 60 | @{ 61 | @"id": @"fee_123", 62 | @"frequency": [NSNull null], // null in dictionary in array 63 | }, 64 | @[ 65 | @"payment", 66 | [NSNull null], // null in array in array 67 | ], 68 | ], 69 | ]; 70 | 71 | NSArray *expected = @[ 72 | @"id", 73 | @{ 74 | @"user": @"user_123", 75 | @"nicknames": @[ 76 | @"john", 77 | @"johnny", 78 | ], 79 | @"profiles": @{ 80 | @"facebook": @"fb_123", 81 | } 82 | }, 83 | @[ 84 | @{ 85 | @"id": @"fee_123", 86 | }, 87 | @[ 88 | @"payment", 89 | ], 90 | ], 91 | ]; 92 | 93 | NSArray *result = [array trc_arrayByRemovingNulls]; 94 | 95 | XCTAssertEqualObjects(result, expected); 96 | } 97 | 98 | - (void)test_arrayByRemovingNulls_keepsEmptyLeaves { 99 | NSArray *array = @[[NSNull null]]; 100 | NSArray *result = [array trc_arrayByRemovingNulls]; 101 | 102 | XCTAssertEqualObjects(result, @[]); 103 | } 104 | 105 | - (void)test_arrayByRemovingNulls_returnsImmutableCopy { 106 | NSArray *array = @[@"id", @"type"]; 107 | NSArray *result = [array trc_arrayByRemovingNulls]; 108 | 109 | XCTAssert(result); 110 | XCTAssertNotEqual(result, array); 111 | XCTAssertFalse([result isKindOfClass:[NSMutableArray class]]); 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /TracerTests/NSDictionary+TracerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDictionary+TracerTests.m 3 | // Tracer 4 | // 5 | // Created by Ben Guo on 7/29/17. 6 | // Copyright © 2017 Stripe. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "NSDictionary+Tracer.h" 12 | #import "TRCJsonDecodable.h" 13 | 14 | @interface NSDictionary_TracerTests : XCTestCase 15 | 16 | @end 17 | 18 | @interface MyDecodable: NSObject 19 | @end 20 | @implementation MyDecodable 21 | + (instancetype)decodedObjectFromJson:(NSDictionary *)json { 22 | MyDecodable *obj = [MyDecodable new]; 23 | if ([json trc_boolForKey:@"invalid" or:NO]) { 24 | return nil; 25 | } 26 | return obj; 27 | } 28 | - (BOOL)isEqual:(id)object { 29 | return [object isMemberOfClass:[self class]]; 30 | } 31 | @end 32 | 33 | @implementation NSDictionary_TracerTests 34 | 35 | - (void)test_dictionaryByRemovingNulls_removesNullsDeeply { 36 | NSDictionary *dictionary = @{ 37 | @"id": @"card_123", 38 | @"tokenization_method": [NSNull null], // null in root 39 | @"metadata": @{ 40 | @"user": @"user_123", 41 | @"country": [NSNull null], // null in dictionary 42 | @"nicknames": @[ 43 | @"john", 44 | @"johnny", 45 | [NSNull null], // null in array in dictionary 46 | ], 47 | @"profiles": @{ 48 | @"facebook": @"fb_123", 49 | @"twitter": [NSNull null], // null in dictionary in dictionary 50 | } 51 | }, 52 | @"fees": @[ 53 | [NSNull null], // null in array 54 | @{ 55 | @"id": @"fee_123", 56 | @"frequency": [NSNull null], // null in dictionary in array 57 | }, 58 | @[ 59 | @"payment", 60 | [NSNull null], // null in array in array 61 | ], 62 | ], 63 | }; 64 | 65 | NSDictionary *expected = @{ 66 | @"id": @"card_123", 67 | @"metadata": @{ 68 | @"user": @"user_123", 69 | @"nicknames": @[ 70 | @"john", 71 | @"johnny", 72 | ], 73 | @"profiles": @{ 74 | @"facebook": @"fb_123", 75 | }, 76 | }, 77 | @"fees": @[ 78 | @{ 79 | @"id": @"fee_123", 80 | }, 81 | @[ 82 | @"payment", 83 | ], 84 | ], 85 | }; 86 | 87 | NSDictionary *result = [dictionary trc_dictionaryByRemovingNulls]; 88 | 89 | XCTAssertEqualObjects(result, expected); 90 | } 91 | 92 | - (void)test_dictionaryByRemovingNulls_keepsEmptyLeaves { 93 | NSDictionary *dictionary = @{@"id": [NSNull null]}; 94 | NSDictionary *result = [dictionary trc_dictionaryByRemovingNulls]; 95 | 96 | XCTAssertEqualObjects(result, @{}); 97 | } 98 | 99 | - (void)test_dictionaryByRemovingNulls_returnsImmutableCopy { 100 | NSDictionary *dictionary = @{@"id": @"card_123"}; 101 | NSDictionary *result = [dictionary trc_dictionaryByRemovingNulls]; 102 | 103 | XCTAssert(result); 104 | XCTAssertNotEqual(result, dictionary); 105 | XCTAssertFalse([result isKindOfClass:[NSMutableDictionary class]]); 106 | } 107 | 108 | #pragma mark - Getters 109 | 110 | - (void)testArrayForKey { 111 | NSDictionary *dict = @{ 112 | @"a": @[@"foo"], 113 | }; 114 | 115 | XCTAssertEqualObjects([dict trc_arrayForKey:@"a"], @[@"foo"]); 116 | XCTAssertNil([dict trc_arrayForKey:@"b"]); 117 | } 118 | 119 | - (void)testBoxedBoolForKey { 120 | NSDictionary *dict = @{ 121 | @"a": @1, 122 | @"b": @0, 123 | @"c": @"true", 124 | @"d": @"false", 125 | @"e": @"1", 126 | @"f": @"foo", 127 | }; 128 | 129 | XCTAssertEqualObjects([dict trc_boxedBoolForKey:@"a"], @YES); 130 | XCTAssertEqualObjects([dict trc_boxedBoolForKey:@"b"], @NO); 131 | XCTAssertEqualObjects([dict trc_boxedBoolForKey:@"c"], @YES); 132 | XCTAssertEqualObjects([dict trc_boxedBoolForKey:@"d"], @NO); 133 | XCTAssertEqualObjects([dict trc_boxedBoolForKey:@"e"], @YES); 134 | XCTAssertEqualObjects([dict trc_boxedBoolForKey:@"f"], @NO); 135 | XCTAssertNil([dict trc_boxedBoolForKey:@"g"]); 136 | } 137 | 138 | - (void)testBoolForKey { 139 | NSDictionary *dict = @{ 140 | @"a": @1, 141 | @"b": @0, 142 | @"c": @"true", 143 | @"d": @"false", 144 | @"e": @"1", 145 | @"f": @"foo", 146 | }; 147 | 148 | XCTAssertTrue([dict trc_boolForKey:@"a" or:NO]); 149 | XCTAssertFalse([dict trc_boolForKey:@"b" or:YES]); 150 | XCTAssertTrue([dict trc_boolForKey:@"c" or:NO]); 151 | XCTAssertFalse([dict trc_boolForKey:@"d" or:YES]); 152 | XCTAssertTrue([dict trc_boolForKey:@"e" or:NO]); 153 | XCTAssertFalse([dict trc_boolForKey:@"f" or:NO]); 154 | XCTAssertTrue([dict trc_boolForKey:@"g" or:YES]); 155 | } 156 | 157 | - (void)testIntForKey { 158 | NSDictionary *dict = @{ 159 | @"a": @1, 160 | @"b": @-1, 161 | @"c": @"1", 162 | @"d": @"-1", 163 | @"e": @"10.0", 164 | @"f": @"10.5", 165 | @"g": @(10.0), 166 | @"h": @(10.5), 167 | @"i": @"foo", 168 | }; 169 | 170 | XCTAssertEqual([dict trc_intForKey:@"a" or:0], 1); 171 | XCTAssertEqual([dict trc_intForKey:@"b" or:0], -1); 172 | XCTAssertEqual([dict trc_intForKey:@"c" or:0], 1); 173 | XCTAssertEqual([dict trc_intForKey:@"d" or:0], -1); 174 | XCTAssertEqual([dict trc_intForKey:@"e" or:0], 10); 175 | XCTAssertEqual([dict trc_intForKey:@"f" or:0], 10); 176 | XCTAssertEqual([dict trc_intForKey:@"g" or:0], 10); 177 | XCTAssertEqual([dict trc_intForKey:@"h" or:0], 10); 178 | XCTAssertEqual([dict trc_intForKey:@"i" or:0], 0); 179 | } 180 | 181 | - (void)testDateForKey { 182 | NSDictionary *dict = @{ 183 | @"a": @0, 184 | @"b": @"0", 185 | }; 186 | NSDate *expectedDate = [NSDate dateWithTimeIntervalSince1970:0]; 187 | 188 | XCTAssertEqualObjects([dict trc_dateForKey:@"a"], expectedDate); 189 | XCTAssertEqualObjects([dict trc_dateForKey:@"b"], expectedDate); 190 | XCTAssertNil([dict trc_dateForKey:@"c"]); 191 | } 192 | 193 | - (void)testDictionaryForKey { 194 | NSDictionary *dict = @{ 195 | @"a": @{@"foo": @"bar"}, 196 | }; 197 | 198 | XCTAssertEqualObjects([dict trc_dictionaryForKey:@"a"], @{@"foo": @"bar"}); 199 | XCTAssertNil([dict trc_dictionaryForKey:@"b"]); 200 | } 201 | 202 | - (void)testNumberForKey { 203 | NSDictionary *dict = @{ 204 | @"a": @1, 205 | }; 206 | 207 | XCTAssertEqualObjects([dict trc_numberForKey:@"a"], @1); 208 | XCTAssertNil([dict trc_numberForKey:@"b"]); 209 | } 210 | 211 | - (void)testStringForKey { 212 | NSDictionary *dict = @{@"a": @"foo"}; 213 | XCTAssertEqualObjects([dict trc_stringForKey:@"a"], @"foo"); 214 | XCTAssertNil([dict trc_stringForKey:@"b"]); 215 | } 216 | 217 | - (void)testURLForKey { 218 | NSDictionary *dict = @{ 219 | @"a": @"https://example.com", 220 | @"b": @"not a url" 221 | }; 222 | XCTAssertEqualObjects([dict trc_urlForKey:@"a"], [NSURL URLWithString:@"https://example.com"]); 223 | XCTAssertNil([dict trc_urlForKey:@"b"]); 224 | XCTAssertNil([dict trc_urlForKey:@"c"]); 225 | } 226 | 227 | - (void)testJsonDecodable { 228 | NSDictionary *json = @{@"a": @"foo", @"b": @"bar"}; 229 | XCTAssertEqualObjects([NSDictionary decodedObjectFromJson:json], json); 230 | } 231 | 232 | - (void)testArrayOfDecodables { 233 | NSDictionary *json = @{ 234 | @"empty": @[], 235 | @"string": @[@""], 236 | @"dictionaries": @[@{}, @{}], 237 | @"invalid-object": @[ 238 | @{@"invalid": @YES}, 239 | @{@"invalid": @NO}, 240 | @{@"invalid": @NO}, 241 | ], 242 | }; 243 | 244 | XCTAssertEqualObjects([json trc_arrayForKey:@"missing" deserializedAs:[MyDecodable class]], 245 | [NSMutableArray new], 246 | @"no array with that key returns empty mutable array"); 247 | 248 | XCTAssertEqualObjects([json trc_arrayForKey:@"empty" deserializedAs:[MyDecodable class]], 249 | [NSMutableArray new], 250 | @"empty array with that key returns empty mutable array"); 251 | 252 | XCTAssertEqualObjects([json trc_arrayForKey:@"string" deserializedAs:[MyDecodable class]], 253 | [NSMutableArray new], 254 | @"if array contains non-dictionaries, they are not deserialized"); 255 | 256 | NSMutableArray *twoDecodedObjects = [@[[MyDecodable new], [MyDecodable new]] mutableCopy]; 257 | 258 | XCTAssertEqualObjects([json trc_arrayForKey:@"dictionaries" deserializedAs:[MyDecodable class]], 259 | twoDecodedObjects, 260 | @"dictionaries are decoded using specified class"); 261 | 262 | XCTAssertEqualObjects([json trc_arrayForKey:@"invalid-object" deserializedAs:[MyDecodable class]], 263 | twoDecodedObjects, 264 | @"if decoder returns nil, omitted from result"); 265 | } 266 | 267 | @end 268 | -------------------------------------------------------------------------------- /TracerTests/RetValueTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RecordPlayProtocolReturnValueTests.m 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 3/3/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "TRCTestTarget.h" 12 | #import "TRCDispatchFunctions.h" 13 | 14 | @interface RetValueTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation RetValueTests 19 | 20 | - (void)testReturnObject { 21 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 22 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 23 | TRCRecorder *recorder = [TRCRecorder new]; 24 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 25 | 26 | NSArray *blocks = @[ 27 | (^{ 28 | [t ret_string__args_none]; 29 | }), 30 | (^{ 31 | [t ret_string__args_int:-100]; 32 | }), 33 | (^{ 34 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 35 | XCTAssertNotNil(trace); 36 | XCTAssertNil(recError); 37 | TRCPlayer *player = [TRCPlayer new]; 38 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 39 | XCTAssertNil(playError); 40 | [exp fulfill]; 41 | }]; 42 | }]; 43 | }), 44 | ]; 45 | 46 | for (NSUInteger i = 0; i < blocks.count; i++) { 47 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 48 | trcDispatchToMainAfter(delay, blocks[i]); 49 | } 50 | 51 | [self waitForExpectationsWithTimeout:10 handler:nil]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /TracerTests/RetVoidMultiArgTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RetVoidMultiArgTests.m 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 3/16/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "TRCTestTarget.h" 12 | #import "TRCDispatchFunctions.h" 13 | 14 | @interface RetVoidMultiArgTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation RetVoidMultiArgTests 19 | 20 | - (void)testMultipleArguments { 21 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 22 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 23 | TRCRecorder *recorder = [TRCRecorder new]; 24 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 25 | 26 | NSArray *blocks = @[ 27 | (^{ 28 | [t ret_void__args_int:100 string:@"string"]; 29 | }), 30 | (^{ 31 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 32 | XCTAssertNotNil(trace); 33 | XCTAssertNil(recError); 34 | TRCPlayer *player = [TRCPlayer new]; 35 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 36 | XCTAssertNil(playError); 37 | [exp fulfill]; 38 | }]; 39 | }]; 40 | }), 41 | ]; 42 | 43 | for (NSUInteger i = 0; i < blocks.count; i++) { 44 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 45 | trcDispatchToMainAfter(delay, blocks[i]); 46 | } 47 | 48 | [self waitForExpectationsWithTimeout:10 handler:nil]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /TracerTests/RetVoidSingleArgTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TracerTests.m 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "TRCTestTarget.h" 12 | #import "TRCDispatchFunctions.h" 13 | 14 | @interface RetVoidSingleArgTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation RetVoidSingleArgTests 19 | 20 | - (void)testPrimitives { 21 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 22 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 23 | TRCRecorder *recorder = [TRCRecorder new]; 24 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 25 | 26 | NSArray *blocks = @[ 27 | (^{ 28 | [t ret_void__args_none]; 29 | }), 30 | (^{ 31 | [t ret_void__args_int:-100]; 32 | }), 33 | (^{ 34 | [t ret_void__args_uint:100]; 35 | }), 36 | (^{ 37 | [t ret_void__args_float:-123.5]; 38 | }), 39 | (^{ 40 | [t ret_void__args_bool:YES]; 41 | }), 42 | (^{ 43 | [t ret_void__args_boxed_int:@(-100)]; 44 | }), 45 | (^{ 46 | [t ret_void__args_boxed_uint:@(100)]; 47 | }), 48 | (^{ 49 | [t ret_void__args_boxed_bool:@(YES)]; 50 | }), 51 | (^{ 52 | [t ret_void__args_string:@"string"]; 53 | }), 54 | (^{ 55 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 56 | XCTAssertNotNil(trace); 57 | XCTAssertNil(recError); 58 | TRCPlayer *player = [TRCPlayer new]; 59 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 60 | XCTAssertNil(playError); 61 | [exp fulfill]; 62 | }]; 63 | }]; 64 | }), 65 | ]; 66 | 67 | for (NSUInteger i = 0; i < blocks.count; i++) { 68 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 69 | trcDispatchToMainAfter(delay, blocks[i]); 70 | } 71 | 72 | [self waitForExpectationsWithTimeout:10 handler:nil]; 73 | } 74 | 75 | - (void)testObjectLiterals { 76 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 77 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 78 | TRCRecorder *recorder = [TRCRecorder new]; 79 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 80 | 81 | NSArray *blocks = @[ 82 | (^{ 83 | [t ret_void__args_boxed_int:@(-100)]; 84 | }), 85 | (^{ 86 | [t ret_void__args_boxed_uint:@(100)]; 87 | }), 88 | (^{ 89 | [t ret_void__args_boxed_bool:@(YES)]; 90 | }), 91 | (^{ 92 | [t ret_void__args_string:@"string"]; 93 | }), 94 | (^{ 95 | [t ret_void__args_array_single:@[@"value1"]]; 96 | }), 97 | (^{ 98 | [t ret_void__args_array_multi:@[ 99 | @"value1", 100 | @(100), 101 | @(-123.5), 102 | @[@"value2", @"value3"] 103 | ]]; 104 | }), 105 | (^{ 106 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 107 | XCTAssertNotNil(trace); 108 | XCTAssertNil(recError); 109 | TRCPlayer *player = [TRCPlayer new]; 110 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 111 | XCTAssertNil(playError); 112 | [exp fulfill]; 113 | }]; 114 | }]; 115 | }), 116 | ]; 117 | 118 | for (NSUInteger i = 0; i < blocks.count; i++) { 119 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 120 | trcDispatchToMainAfter(delay, blocks[i]); 121 | } 122 | 123 | [self waitForExpectationsWithTimeout:10 handler:nil]; 124 | } 125 | 126 | - (void)testObjectReferences { 127 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 128 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 129 | TRCRecorder *recorder = [TRCRecorder new]; 130 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 131 | 132 | NSArray *blocks = @[ 133 | (^{ 134 | NSNumber *n = [NSNumber numberWithInt:-100]; 135 | [t ret_void__args_boxed_int:n]; 136 | }), 137 | (^{ 138 | NSNumber *n = [NSNumber numberWithUnsignedInt:100]; 139 | [t ret_void__args_boxed_uint:n]; 140 | }), 141 | (^{ 142 | NSNumber *n = [NSNumber numberWithFloat:-123.5]; 143 | [t ret_void__args_boxed_float:n]; 144 | }), 145 | (^{ 146 | NSNumber *n = [NSNumber numberWithBool:YES]; 147 | [t ret_void__args_boxed_bool:n]; 148 | }), 149 | (^{ 150 | NSString *s = @"string"; 151 | [t ret_void__args_string:s]; 152 | }), 153 | (^{ 154 | NSDictionary *d = @{ 155 | @"key1": @"value1", 156 | }; 157 | [t ret_void__args_dict_single:d]; 158 | }), 159 | (^{ 160 | NSArray *a = @[ 161 | @"value1", 162 | ]; 163 | [t ret_void__args_array_single:a]; 164 | }), 165 | (^{ 166 | NSArray *a = @[ 167 | @"value1", 168 | @(100), 169 | @(-123.5), 170 | @[@"value2", @"value3"] 171 | ]; 172 | [t ret_void__args_array_multi:a]; 173 | }), 174 | (^{ 175 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 176 | XCTAssertNotNil(trace); 177 | XCTAssertNil(recError); 178 | TRCPlayer *player = [TRCPlayer new]; 179 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 180 | XCTAssertNil(playError); 181 | [exp fulfill]; 182 | }]; 183 | }]; 184 | }), 185 | ]; 186 | 187 | for (NSUInteger i = 0; i < blocks.count; i++) { 188 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 189 | trcDispatchToMainAfter(delay, blocks[i]); 190 | } 191 | 192 | [self waitForExpectationsWithTimeout:10 handler:nil]; 193 | } 194 | 195 | #pragma mark - Regression tests 196 | 197 | /** 198 | Boxed float playback failed until Player retained object args 199 | */ 200 | - (void)testBoxedFloatLiteral { 201 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 202 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 203 | TRCRecorder *recorder = [TRCRecorder new]; 204 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 205 | 206 | NSArray *blocks = @[ 207 | (^{ 208 | [t ret_void__args_boxed_float:@(-123.5)]; 209 | }), 210 | (^{ 211 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 212 | XCTAssertNotNil(trace); 213 | XCTAssertNil(recError); 214 | TRCPlayer *player = [TRCPlayer new]; 215 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 216 | XCTAssertNil(playError); 217 | [exp fulfill]; 218 | }]; 219 | }]; 220 | }), 221 | ]; 222 | 223 | for (NSUInteger i = 0; i < blocks.count; i++) { 224 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 225 | trcDispatchToMainAfter(delay, blocks[i]); 226 | } 227 | 228 | [self waitForExpectationsWithTimeout:10 handler:nil]; 229 | } 230 | 231 | 232 | /** 233 | Dictionary playback failed until Player retained object args 234 | */ 235 | - (void)testDictionary { 236 | XCTestExpectation *exp = [self expectationWithDescription:@"done"]; 237 | TRCTestTarget *t = [[TRCTestTarget alloc] init]; 238 | TRCRecorder *recorder = [TRCRecorder new]; 239 | [recorder startRecording:t protocol:@protocol(TRCTestProtocol)]; 240 | 241 | NSArray *blocks = @[ 242 | (^{ 243 | [t ret_void__args_dict_single:@{ 244 | @"key1": @"value1", 245 | }]; 246 | }), 247 | (^{ 248 | NSDictionary *d = @{ 249 | @"key1": @"value1", 250 | }; 251 | [t ret_void__args_dict_single:d]; 252 | }), 253 | (^{ 254 | NSMutableDictionary *d = [@{ 255 | @"key1": @"value1", 256 | } mutableCopy]; 257 | [t ret_void__args_dict_single:d]; 258 | }), 259 | (^{ 260 | [t ret_void__args_dict_multi:@{ 261 | @"key1": @"value1", 262 | @"key2": @{ 263 | @"key3": @[@"value2", @"value3"], 264 | @"key4": @{ 265 | @"key5": @(-123.5), 266 | }, 267 | }, 268 | }]; 269 | }), 270 | (^{ 271 | [recorder stopRecording:t protocol:@protocol(TRCTestProtocol) completion:^(TRCTrace * _Nullable trace, NSError * _Nullable recError) { 272 | XCTAssertNotNil(trace); 273 | XCTAssertNil(recError); 274 | TRCPlayer *player = [TRCPlayer new]; 275 | [player playTrace:trace onTarget:t completion:^(NSError * _Nullable playError) { 276 | XCTAssertNil(playError); 277 | [exp fulfill]; 278 | }]; 279 | }]; 280 | }), 281 | ]; 282 | 283 | for (NSUInteger i = 0; i < blocks.count; i++) { 284 | NSTimeInterval delay = (NSTimeInterval)(0.1*(i+1)); 285 | trcDispatchToMainAfter(delay, blocks[i]); 286 | } 287 | 288 | [self waitForExpectationsWithTimeout:10 handler:nil]; 289 | } 290 | 291 | @end 292 | -------------------------------------------------------------------------------- /TracerTests/TRCTestTarget.h: -------------------------------------------------------------------------------- 1 | // 2 | // TestSubject.h 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @class TRCCustomObject; 14 | 15 | /** 16 | Unit tests are intentionally mock-free, for simplicity. 17 | All method implementations assert on hardcoded expected values. 18 | 19 | TODO: investigate why optional protocol methods don't get hooked 20 | */ 21 | @protocol TRCTestProtocol 22 | - (void)ret_void__args_none; 23 | - (void)ret_void__args_int:(int)i; 24 | - (void)ret_void__args_uint:(uint)i; 25 | - (void)ret_void__args_float:(float)f; 26 | - (void)ret_void__args_bool:(BOOL)b; 27 | - (void)ret_void__args_string:(NSString *)s; 28 | - (void)ret_void__args_boxed_int:(NSNumber *)n; 29 | - (void)ret_void__args_boxed_uint:(NSNumber *)n; 30 | - (void)ret_void__args_boxed_bool:(NSNumber *)n; 31 | - (void)ret_void__args_boxed_float:(NSNumber *)n; 32 | - (void)ret_void__args_array_single:(NSArray *)a; 33 | - (void)ret_void__args_array_multi:(NSArray *)a; 34 | - (void)ret_void__args_dict_single:(NSDictionary *)d; 35 | - (void)ret_void__args_dict_multi:(NSDictionary *)d; 36 | - (void)ret_void__args_int:(int)i string:(NSString *)s; 37 | - (NSString *)ret_string__args_none; 38 | - (NSString *)ret_string__args_int:(int)i; 39 | 40 | // unsupported 41 | - (void)ret_void__args_object:(NSObject *)o; 42 | - (void)ret_void__args_cgsize:(CGSize)s; 43 | 44 | // fixture provider 45 | - (void)ret_void__args_custom_object:(TRCCustomObject *)o; 46 | - (void)ret_void__args_custom_object_array:(NSArray*)a; 47 | 48 | @end 49 | 50 | @interface TRCTestTarget : NSObject 51 | 52 | @end 53 | 54 | @interface TRCCustomObject : NSObject 55 | @property (nonatomic, strong) NSString *value; 56 | @end 57 | 58 | NS_ASSUME_NONNULL_END 59 | -------------------------------------------------------------------------------- /TracerTests/TRCTestTarget.m: -------------------------------------------------------------------------------- 1 | // 2 | // TestSubject.m 3 | // TracerTests 4 | // 5 | // Created by Ben Guo on 2/22/19. 6 | // Copyright © 2019 tracer. All rights reserved. 7 | // 8 | 9 | #import "TRCTestTarget.h" 10 | 11 | #import "TRCDispatchFunctions.h" 12 | 13 | @implementation TRCTestTarget 14 | 15 | #pragma mark - public methods - return void 16 | 17 | - (void)ret_void__args_none { 18 | NSLog(@"%@", NSStringFromSelector(_cmd)); 19 | } 20 | 21 | - (void)ret_void__args_int:(int)i { 22 | NSLog(@"%@", NSStringFromSelector(_cmd)); 23 | NSParameterAssert(i == -100); 24 | } 25 | 26 | - (void)ret_void__args_uint:(uint)i { 27 | NSLog(@"%@", NSStringFromSelector(_cmd)); 28 | NSParameterAssert(i == 100); 29 | } 30 | 31 | - (void)ret_void__args_float:(float)f { 32 | NSLog(@"%@", NSStringFromSelector(_cmd)); 33 | NSParameterAssert(f == -123.5); 34 | } 35 | 36 | - (void)ret_void__args_bool:(BOOL)b { 37 | NSLog(@"%@", NSStringFromSelector(_cmd)); 38 | NSParameterAssert(b == YES); 39 | } 40 | 41 | - (void)ret_void__args_string:(NSString *)s { 42 | NSLog(@"%@", NSStringFromSelector(_cmd)); 43 | NSParameterAssert([s isEqualToString:@"string"]); 44 | } 45 | 46 | - (void)ret_void__args_array_single:(NSArray *)a { 47 | NSLog(@"%@", NSStringFromSelector(_cmd)); 48 | NSArray *expected = @[ 49 | @"value1", 50 | ]; 51 | NSParameterAssert([a isEqualToArray:expected]); 52 | } 53 | 54 | - (void)ret_void__args_array_multi:(NSArray *)a { 55 | NSLog(@"%@", NSStringFromSelector(_cmd)); 56 | NSArray *expected = @[ 57 | @"value1", 58 | @(100), 59 | @(-123.5), 60 | @[@"value2", @"value3"] 61 | ]; 62 | NSParameterAssert([a isEqualToArray:expected]); 63 | } 64 | 65 | - (void)ret_void__args_boxed_int:(NSNumber *)n { 66 | NSLog(@"%@", NSStringFromSelector(_cmd)); 67 | NSParameterAssert([n isEqual:@(-100)]); 68 | } 69 | 70 | - (void)ret_void__args_boxed_uint:(NSNumber *)n { 71 | NSLog(@"%@", NSStringFromSelector(_cmd)); 72 | NSParameterAssert([n unsignedIntegerValue] == 100); 73 | } 74 | 75 | - (void)ret_void__args_boxed_float:(NSNumber *)n { 76 | NSParameterAssert(n != nil); 77 | NSLog(@"%@", NSStringFromSelector(_cmd)); 78 | NSParameterAssert([n floatValue] == -123.5); 79 | } 80 | 81 | - (void)ret_void__args_boxed_bool:(NSNumber *)n { 82 | NSLog(@"%@", NSStringFromSelector(_cmd)); 83 | NSParameterAssert([n boolValue] == YES); 84 | } 85 | 86 | - (void)ret_void__args_dict_single:(NSDictionary *)d { 87 | NSLog(@"%@", NSStringFromSelector(_cmd)); 88 | NSDictionary *expected = @{@"key1": @"value1"}; 89 | NSParameterAssert([d isEqualToDictionary:expected]); 90 | } 91 | 92 | - (void)ret_void__args_dictMultiEntry:(NSDictionary *)d { 93 | NSLog(@"%@", NSStringFromSelector(_cmd)); 94 | NSDictionary *expected = @{ 95 | @"key1": @"value1", 96 | @"key2": @"value2" 97 | }; 98 | NSParameterAssert([d isEqualToDictionary:expected]); 99 | } 100 | 101 | - (void)ret_void__args_dict_multi:(NSDictionary *)d { 102 | NSLog(@"%@", NSStringFromSelector(_cmd)); 103 | NSDictionary *expected = @{ 104 | @"key1": @"value1", 105 | @"key2": @{ 106 | @"key3": @[@"value2", @"value3"], 107 | @"key4": @{ 108 | @"key5": @(-123.5), 109 | }, 110 | }, 111 | }; 112 | NSParameterAssert([d isEqualToDictionary:expected]); 113 | } 114 | 115 | - (void)ret_void__args_int:(int)i string:(NSString *)s { 116 | NSLog(@"%@", NSStringFromSelector(_cmd)); 117 | NSParameterAssert(i == 100); 118 | NSParameterAssert([s isEqualToString:@"string"]); 119 | } 120 | 121 | #pragma mark - public methods - return value 122 | 123 | - (NSString *)ret_string__args_none { 124 | NSLog(@"%@", NSStringFromSelector(_cmd)); 125 | return @"string"; 126 | } 127 | 128 | - (NSString *)ret_string__args_int:(int)i { 129 | NSLog(@"%@", NSStringFromSelector(_cmd)); 130 | NSParameterAssert(i == -100); 131 | return @"string"; 132 | } 133 | 134 | #pragma mark - private methods 135 | 136 | - (void)private_method { 137 | NSLog(@"%@", NSStringFromSelector(_cmd)); 138 | } 139 | 140 | - (void)private_method_primitive:(NSInteger)p { 141 | NSLog(@"%@", NSStringFromSelector(_cmd)); 142 | } 143 | 144 | - (void)private_method_string:(NSString *)s { 145 | NSLog(@"%@", NSStringFromSelector(_cmd)); 146 | } 147 | 148 | #pragma mark - unsupported methods 149 | 150 | - (void)ret_void__args_object:(NSObject *)o { 151 | NSLog(@"%@", NSStringFromSelector(_cmd)); 152 | } 153 | 154 | - (void)ret_void__args_cgsize:(CGSize)s { 155 | NSLog(@"%@", NSStringFromCGSize(s)); 156 | } 157 | 158 | #pragma mark - fixture provider 159 | 160 | - (void)ret_void__args_custom_object:(TRCCustomObject *)o { 161 | NSLog(@"%@", o); 162 | NSParameterAssert([o.value isEqualToString:@"one"]); 163 | } 164 | 165 | - (void)ret_void__args_custom_object_array:(NSArray*)a { 166 | NSLog(@"%@", a); 167 | NSParameterAssert(a.count == 3); 168 | TRCCustomObject *one = a[0]; 169 | TRCCustomObject *two = a[1]; 170 | TRCCustomObject *three = a[2]; 171 | NSParameterAssert([one.value isEqualToString:@"one"]); 172 | NSParameterAssert([two.value isEqualToString:@"two"]); 173 | NSParameterAssert([three.value isEqualToString:@"three"]); 174 | } 175 | 176 | @end 177 | 178 | @implementation TRCCustomObject : NSObject 179 | @end 180 | -------------------------------------------------------------------------------- /TracerTests/saved_trace.json: -------------------------------------------------------------------------------- 1 | { 2 | "start_ms" : 1555567078880, 3 | "trace_object" : "trace", 4 | "protocol" : "TRCTestProtocol", 5 | "calls" : [ 6 | { 7 | "arguments" : [ 8 | 9 | ], 10 | "trace_object" : "call", 11 | "method" : "ret_void__args_none", 12 | "start_ms" : 104, 13 | "return_value" : { 14 | "trace_object" : "value", 15 | "type" : "void", 16 | "object_type" : "not_an_object" 17 | } 18 | }, 19 | { 20 | "arguments" : [ 21 | { 22 | "trace_object" : "value", 23 | "type" : "int", 24 | "object_type" : "not_an_object", 25 | "object_value" : -100 26 | } 27 | ], 28 | "trace_object" : "call", 29 | "method" : "ret_void__args_int:", 30 | "start_ms" : 204, 31 | "return_value" : { 32 | "trace_object" : "value", 33 | "type" : "void", 34 | "object_type" : "not_an_object" 35 | } 36 | }, 37 | { 38 | "arguments" : [ 39 | { 40 | "trace_object" : "value", 41 | "type" : "unsigned_int", 42 | "object_type" : "not_an_object", 43 | "object_value" : 100 44 | } 45 | ], 46 | "trace_object" : "call", 47 | "method" : "ret_void__args_uint:", 48 | "start_ms" : 305, 49 | "return_value" : { 50 | "trace_object" : "value", 51 | "type" : "void", 52 | "object_type" : "not_an_object" 53 | } 54 | }, 55 | { 56 | "arguments" : [ 57 | { 58 | "trace_object" : "value", 59 | "type" : "float", 60 | "object_type" : "not_an_object", 61 | "object_value" : -123.5 62 | } 63 | ], 64 | "trace_object" : "call", 65 | "method" : "ret_void__args_float:", 66 | "start_ms" : 404, 67 | "return_value" : { 68 | "trace_object" : "value", 69 | "type" : "void", 70 | "object_type" : "not_an_object" 71 | } 72 | }, 73 | { 74 | "arguments" : [ 75 | { 76 | "trace_object" : "value", 77 | "type" : "bool", 78 | "object_type" : "not_an_object", 79 | "object_value" : true 80 | } 81 | ], 82 | "trace_object" : "call", 83 | "method" : "ret_void__args_bool:", 84 | "start_ms" : 504, 85 | "return_value" : { 86 | "trace_object" : "value", 87 | "type" : "void", 88 | "object_type" : "not_an_object" 89 | } 90 | }, 91 | { 92 | "arguments" : [ 93 | { 94 | "object_class" : "NSNumber", 95 | "trace_object" : "value", 96 | "type" : "object", 97 | "object_type" : "json_object", 98 | "object_value" : -100 99 | } 100 | ], 101 | "trace_object" : "call", 102 | "method" : "ret_void__args_boxed_int:", 103 | "start_ms" : 605, 104 | "return_value" : { 105 | "trace_object" : "value", 106 | "type" : "void", 107 | "object_type" : "not_an_object" 108 | } 109 | }, 110 | { 111 | "arguments" : [ 112 | { 113 | "object_class" : "NSNumber", 114 | "trace_object" : "value", 115 | "type" : "object", 116 | "object_type" : "json_object", 117 | "object_value" : -123.5 118 | } 119 | ], 120 | "trace_object" : "call", 121 | "method" : "ret_void__args_boxed_float:", 122 | "start_ms" : 704, 123 | "return_value" : { 124 | "trace_object" : "value", 125 | "type" : "void", 126 | "object_type" : "not_an_object" 127 | } 128 | }, 129 | { 130 | "arguments" : [ 131 | { 132 | "object_class" : "NSNumber", 133 | "trace_object" : "value", 134 | "type" : "object", 135 | "object_type" : "json_object", 136 | "object_value" : 100 137 | } 138 | ], 139 | "trace_object" : "call", 140 | "method" : "ret_void__args_boxed_uint:", 141 | "start_ms" : 807, 142 | "return_value" : { 143 | "trace_object" : "value", 144 | "type" : "void", 145 | "object_type" : "not_an_object" 146 | } 147 | }, 148 | { 149 | "arguments" : [ 150 | { 151 | "object_class" : "NSNumber", 152 | "trace_object" : "value", 153 | "type" : "object", 154 | "object_type" : "json_object", 155 | "object_value" : true 156 | } 157 | ], 158 | "trace_object" : "call", 159 | "method" : "ret_void__args_boxed_bool:", 160 | "start_ms" : 906, 161 | "return_value" : { 162 | "trace_object" : "value", 163 | "type" : "void", 164 | "object_type" : "not_an_object" 165 | } 166 | }, 167 | { 168 | "arguments" : [ 169 | { 170 | "object_class" : "NSString", 171 | "trace_object" : "value", 172 | "type" : "object", 173 | "object_type" : "json_object", 174 | "object_value" : "string" 175 | } 176 | ], 177 | "trace_object" : "call", 178 | "method" : "ret_void__args_string:", 179 | "start_ms" : 1004, 180 | "return_value" : { 181 | "trace_object" : "value", 182 | "type" : "void", 183 | "object_type" : "not_an_object" 184 | } 185 | }, 186 | { 187 | "arguments" : [ 188 | { 189 | "trace_object" : "value", 190 | "type" : "int", 191 | "object_type" : "not_an_object", 192 | "object_value" : 100 193 | }, 194 | { 195 | "object_class" : "NSString", 196 | "trace_object" : "value", 197 | "type" : "object", 198 | "object_type" : "json_object", 199 | "object_value" : "string" 200 | } 201 | ], 202 | "trace_object" : "call", 203 | "method" : "ret_void__args_int:string:", 204 | "start_ms" : 1107, 205 | "return_value" : { 206 | "trace_object" : "value", 207 | "type" : "void", 208 | "object_type" : "not_an_object" 209 | } 210 | }, 211 | { 212 | "arguments" : [ 213 | { 214 | "object_class" : "NSNumber", 215 | "trace_object" : "value", 216 | "type" : "object", 217 | "object_type" : "json_object", 218 | "object_value" : -100 219 | } 220 | ], 221 | "trace_object" : "call", 222 | "method" : "ret_void__args_boxed_int:", 223 | "start_ms" : 1204, 224 | "return_value" : { 225 | "trace_object" : "value", 226 | "type" : "void", 227 | "object_type" : "not_an_object" 228 | } 229 | }, 230 | { 231 | "arguments" : [ 232 | { 233 | "object_class" : "NSNumber", 234 | "trace_object" : "value", 235 | "type" : "object", 236 | "object_type" : "json_object", 237 | "object_value" : 100 238 | } 239 | ], 240 | "trace_object" : "call", 241 | "method" : "ret_void__args_boxed_uint:", 242 | "start_ms" : 1306, 243 | "return_value" : { 244 | "trace_object" : "value", 245 | "type" : "void", 246 | "object_type" : "not_an_object" 247 | } 248 | }, 249 | { 250 | "arguments" : [ 251 | { 252 | "object_class" : "NSNumber", 253 | "trace_object" : "value", 254 | "type" : "object", 255 | "object_type" : "json_object", 256 | "object_value" : true 257 | } 258 | ], 259 | "trace_object" : "call", 260 | "method" : "ret_void__args_boxed_bool:", 261 | "start_ms" : 1404, 262 | "return_value" : { 263 | "trace_object" : "value", 264 | "type" : "void", 265 | "object_type" : "not_an_object" 266 | } 267 | }, 268 | { 269 | "arguments" : [ 270 | { 271 | "object_class" : "NSString", 272 | "trace_object" : "value", 273 | "type" : "object", 274 | "object_type" : "json_object", 275 | "object_value" : "string" 276 | } 277 | ], 278 | "trace_object" : "call", 279 | "method" : "ret_void__args_string:", 280 | "start_ms" : 1506, 281 | "return_value" : { 282 | "trace_object" : "value", 283 | "type" : "void", 284 | "object_type" : "not_an_object" 285 | } 286 | }, 287 | { 288 | "arguments" : [ 289 | { 290 | "object_class" : "__NSSingleObjectArrayI", 291 | "trace_object" : "value", 292 | "type" : "object", 293 | "object_type" : "json_object", 294 | "object_value" : [ 295 | "value1" 296 | ] 297 | } 298 | ], 299 | "trace_object" : "call", 300 | "method" : "ret_void__args_array_single:", 301 | "start_ms" : 1604, 302 | "return_value" : { 303 | "trace_object" : "value", 304 | "type" : "void", 305 | "object_type" : "not_an_object" 306 | } 307 | }, 308 | { 309 | "arguments" : [ 310 | { 311 | "object_class" : "__NSArrayI", 312 | "trace_object" : "value", 313 | "type" : "object", 314 | "object_type" : "json_object", 315 | "object_value" : [ 316 | "value1", 317 | 100, 318 | -123.5, 319 | [ 320 | "value2", 321 | "value3" 322 | ] 323 | ] 324 | } 325 | ], 326 | "trace_object" : "call", 327 | "method" : "ret_void__args_array_multi:", 328 | "start_ms" : 1704, 329 | "return_value" : { 330 | "trace_object" : "value", 331 | "type" : "void", 332 | "object_type" : "not_an_object" 333 | } 334 | }, 335 | { 336 | "arguments" : [ 337 | { 338 | "object_class" : "NSNumber", 339 | "trace_object" : "value", 340 | "type" : "object", 341 | "object_type" : "json_object", 342 | "object_value" : -100 343 | } 344 | ], 345 | "trace_object" : "call", 346 | "method" : "ret_void__args_boxed_int:", 347 | "start_ms" : 1804, 348 | "return_value" : { 349 | "trace_object" : "value", 350 | "type" : "void", 351 | "object_type" : "not_an_object" 352 | } 353 | }, 354 | { 355 | "arguments" : [ 356 | { 357 | "object_class" : "NSNumber", 358 | "trace_object" : "value", 359 | "type" : "object", 360 | "object_type" : "json_object", 361 | "object_value" : 100 362 | } 363 | ], 364 | "trace_object" : "call", 365 | "method" : "ret_void__args_boxed_uint:", 366 | "start_ms" : 1906, 367 | "return_value" : { 368 | "trace_object" : "value", 369 | "type" : "void", 370 | "object_type" : "not_an_object" 371 | } 372 | }, 373 | { 374 | "arguments" : [ 375 | { 376 | "object_class" : "NSNumber", 377 | "trace_object" : "value", 378 | "type" : "object", 379 | "object_type" : "json_object", 380 | "object_value" : -123.5 381 | } 382 | ], 383 | "trace_object" : "call", 384 | "method" : "ret_void__args_boxed_float:", 385 | "start_ms" : 2006, 386 | "return_value" : { 387 | "trace_object" : "value", 388 | "type" : "void", 389 | "object_type" : "not_an_object" 390 | } 391 | }, 392 | { 393 | "arguments" : [ 394 | { 395 | "object_class" : "NSNumber", 396 | "trace_object" : "value", 397 | "type" : "object", 398 | "object_type" : "json_object", 399 | "object_value" : true 400 | } 401 | ], 402 | "trace_object" : "call", 403 | "method" : "ret_void__args_boxed_bool:", 404 | "start_ms" : 2106, 405 | "return_value" : { 406 | "trace_object" : "value", 407 | "type" : "void", 408 | "object_type" : "not_an_object" 409 | } 410 | }, 411 | { 412 | "arguments" : [ 413 | { 414 | "object_class" : "NSString", 415 | "trace_object" : "value", 416 | "type" : "object", 417 | "object_type" : "json_object", 418 | "object_value" : "string" 419 | } 420 | ], 421 | "trace_object" : "call", 422 | "method" : "ret_void__args_string:", 423 | "start_ms" : 2205, 424 | "return_value" : { 425 | "trace_object" : "value", 426 | "type" : "void", 427 | "object_type" : "not_an_object" 428 | } 429 | }, 430 | { 431 | "arguments" : [ 432 | { 433 | "object_class" : "__NSSingleEntryDictionaryI", 434 | "trace_object" : "value", 435 | "type" : "object", 436 | "object_type" : "json_object", 437 | "object_value" : { 438 | "key1" : "value1" 439 | } 440 | } 441 | ], 442 | "trace_object" : "call", 443 | "method" : "ret_void__args_dict_single:", 444 | "start_ms" : 2304, 445 | "return_value" : { 446 | "trace_object" : "value", 447 | "type" : "void", 448 | "object_type" : "not_an_object" 449 | } 450 | }, 451 | { 452 | "arguments" : [ 453 | { 454 | "object_class" : "__NSSingleObjectArrayI", 455 | "trace_object" : "value", 456 | "type" : "object", 457 | "object_type" : "json_object", 458 | "object_value" : [ 459 | "value1" 460 | ] 461 | } 462 | ], 463 | "trace_object" : "call", 464 | "method" : "ret_void__args_array_single:", 465 | "start_ms" : 2405, 466 | "return_value" : { 467 | "trace_object" : "value", 468 | "type" : "void", 469 | "object_type" : "not_an_object" 470 | } 471 | }, 472 | { 473 | "arguments" : [ 474 | { 475 | "object_class" : "__NSArrayI", 476 | "trace_object" : "value", 477 | "type" : "object", 478 | "object_type" : "json_object", 479 | "object_value" : [ 480 | "value1", 481 | 100, 482 | -123.5, 483 | [ 484 | "value2", 485 | "value3" 486 | ] 487 | ] 488 | } 489 | ], 490 | "trace_object" : "call", 491 | "method" : "ret_void__args_array_multi:", 492 | "start_ms" : 2504, 493 | "return_value" : { 494 | "trace_object" : "value", 495 | "type" : "void", 496 | "object_type" : "not_an_object" 497 | } 498 | }, 499 | { 500 | "arguments" : [ 501 | { 502 | "object_class" : "__NSSingleEntryDictionaryI", 503 | "trace_object" : "value", 504 | "type" : "object", 505 | "object_type" : "json_object", 506 | "object_value" : { 507 | "key1" : "value1" 508 | } 509 | } 510 | ], 511 | "trace_object" : "call", 512 | "method" : "ret_void__args_dict_single:", 513 | "start_ms" : 2605, 514 | "return_value" : { 515 | "trace_object" : "value", 516 | "type" : "void", 517 | "object_type" : "not_an_object" 518 | } 519 | }, 520 | { 521 | "arguments" : [ 522 | { 523 | "object_class" : "__NSSingleEntryDictionaryI", 524 | "trace_object" : "value", 525 | "type" : "object", 526 | "object_type" : "json_object", 527 | "object_value" : { 528 | "key1" : "value1" 529 | } 530 | } 531 | ], 532 | "trace_object" : "call", 533 | "method" : "ret_void__args_dict_single:", 534 | "start_ms" : 2705, 535 | "return_value" : { 536 | "trace_object" : "value", 537 | "type" : "void", 538 | "object_type" : "not_an_object" 539 | } 540 | }, 541 | { 542 | "arguments" : [ 543 | { 544 | "object_class" : "__NSDictionaryM", 545 | "trace_object" : "value", 546 | "type" : "object", 547 | "object_type" : "json_object", 548 | "object_value" : { 549 | "key1" : "value1" 550 | } 551 | } 552 | ], 553 | "trace_object" : "call", 554 | "method" : "ret_void__args_dict_single:", 555 | "start_ms" : 2806, 556 | "return_value" : { 557 | "trace_object" : "value", 558 | "type" : "void", 559 | "object_type" : "not_an_object" 560 | } 561 | }, 562 | { 563 | "arguments" : [ 564 | { 565 | "object_class" : "__NSDictionaryI", 566 | "trace_object" : "value", 567 | "type" : "object", 568 | "object_type" : "json_object", 569 | "object_value" : { 570 | "key1" : "value1", 571 | "key2" : { 572 | "key4" : { 573 | "key5" : -123.5 574 | }, 575 | "key3" : [ 576 | "value2", 577 | "value3" 578 | ] 579 | } 580 | } 581 | } 582 | ], 583 | "trace_object" : "call", 584 | "method" : "ret_void__args_dict_multi:", 585 | "start_ms" : 2907, 586 | "return_value" : { 587 | "trace_object" : "value", 588 | "type" : "void", 589 | "object_type" : "not_an_object" 590 | } 591 | } 592 | ] 593 | } 594 | -------------------------------------------------------------------------------- /TracerTests/trace_bt_scan_connect.json: -------------------------------------------------------------------------------- 1 | { 2 | "protocol" : "SCPBBDeviceControllerProxy", 3 | "trace_object" : "trace", 4 | "start_ms" : 4294967295, 5 | "calls" : [ 6 | { 7 | "method" : "startBTScan:scanTimeout:", 8 | "trace_object" : "call", 9 | "arguments" : [ 10 | { 11 | "type" : "object", 12 | "trace_object" : "value", 13 | "object_type" : "json_object", 14 | "object_value" : [ 15 | "CHB20", 16 | "CHB22" 17 | ], 18 | "object_class" : "__NSArrayM" 19 | }, 20 | { 21 | "type" : "int", 22 | "trace_object" : "value", 23 | "object_value" : 0, 24 | "object_type" : "not_an_object" 25 | } 26 | ], 27 | "start_ms" : 118, 28 | "return_value" : { 29 | "trace_object" : "value", 30 | "type" : "void", 31 | "object_type" : "not_an_object" 32 | } 33 | }, 34 | { 35 | "method" : "isDebugLogEnabled", 36 | "trace_object" : "call", 37 | "arguments" : [ 38 | 39 | ], 40 | "start_ms" : 119, 41 | "return_value" : { 42 | "type" : "char", 43 | "trace_object" : "value", 44 | "object_value" : 0, 45 | "object_type" : "not_an_object" 46 | } 47 | }, 48 | { 49 | "method" : "isDebugLogEnabled", 50 | "trace_object" : "call", 51 | "arguments" : [ 52 | 53 | ], 54 | "start_ms" : 271, 55 | "return_value" : { 56 | "type" : "char", 57 | "trace_object" : "value", 58 | "object_value" : 0, 59 | "object_type" : "not_an_object" 60 | } 61 | }, 62 | { 63 | "method" : "isDebugLogEnabled", 64 | "trace_object" : "call", 65 | "arguments" : [ 66 | 67 | ], 68 | "start_ms" : 272, 69 | "return_value" : { 70 | "type" : "char", 71 | "trace_object" : "value", 72 | "object_value" : 0, 73 | "object_type" : "not_an_object" 74 | } 75 | }, 76 | { 77 | "method" : "isDebugLogEnabled", 78 | "trace_object" : "call", 79 | "arguments" : [ 80 | 81 | ], 82 | "start_ms" : 293, 83 | "return_value" : { 84 | "type" : "char", 85 | "trace_object" : "value", 86 | "object_value" : 0, 87 | "object_type" : "not_an_object" 88 | } 89 | }, 90 | { 91 | "method" : "isDebugLogEnabled", 92 | "trace_object" : "call", 93 | "arguments" : [ 94 | 95 | ], 96 | "start_ms" : 316, 97 | "return_value" : { 98 | "type" : "char", 99 | "trace_object" : "value", 100 | "object_value" : 0, 101 | "object_type" : "not_an_object" 102 | } 103 | }, 104 | { 105 | "method" : "isDebugLogEnabled", 106 | "trace_object" : "call", 107 | "arguments" : [ 108 | 109 | ], 110 | "start_ms" : 324, 111 | "return_value" : { 112 | "type" : "char", 113 | "trace_object" : "value", 114 | "object_value" : 0, 115 | "object_type" : "not_an_object" 116 | } 117 | }, 118 | { 119 | "method" : "isDebugLogEnabled", 120 | "trace_object" : "call", 121 | "arguments" : [ 122 | 123 | ], 124 | "start_ms" : 355, 125 | "return_value" : { 126 | "type" : "char", 127 | "trace_object" : "value", 128 | "object_value" : 0, 129 | "object_type" : "not_an_object" 130 | } 131 | }, 132 | { 133 | "method" : "isDebugLogEnabled", 134 | "trace_object" : "call", 135 | "arguments" : [ 136 | 137 | ], 138 | "start_ms" : 564, 139 | "return_value" : { 140 | "type" : "char", 141 | "trace_object" : "value", 142 | "object_value" : 0, 143 | "object_type" : "not_an_object" 144 | } 145 | }, 146 | { 147 | "method" : "isDebugLogEnabled", 148 | "trace_object" : "call", 149 | "arguments" : [ 150 | 151 | ], 152 | "start_ms" : 565, 153 | "return_value" : { 154 | "type" : "char", 155 | "trace_object" : "value", 156 | "object_value" : 0, 157 | "object_type" : "not_an_object" 158 | } 159 | }, 160 | { 161 | "method" : "stopBTScan", 162 | "trace_object" : "call", 163 | "arguments" : [ 164 | 165 | ], 166 | "start_ms" : 2296, 167 | "return_value" : { 168 | "trace_object" : "value", 169 | "type" : "void", 170 | "object_type" : "not_an_object" 171 | } 172 | }, 173 | { 174 | "method" : "isDebugLogEnabled", 175 | "trace_object" : "call", 176 | "arguments" : [ 177 | 178 | ], 179 | "start_ms" : 2296, 180 | "return_value" : { 181 | "type" : "char", 182 | "trace_object" : "value", 183 | "object_value" : 0, 184 | "object_type" : "not_an_object" 185 | } 186 | }, 187 | { 188 | "method" : "isDebugLogEnabled", 189 | "trace_object" : "call", 190 | "arguments" : [ 191 | 192 | ], 193 | "start_ms" : 2297, 194 | "return_value" : { 195 | "type" : "char", 196 | "trace_object" : "value", 197 | "object_value" : 0, 198 | "object_type" : "not_an_object" 199 | } 200 | }, 201 | { 202 | "method" : "connectBTWithUUID:", 203 | "trace_object" : "call", 204 | "arguments" : [ 205 | { 206 | "type" : "object", 207 | "trace_object" : "value", 208 | "object_type" : "json_object", 209 | "object_value" : "513D55EF-1996-A592-E3EF-B29A734EAE41", 210 | "object_class" : "NSString" 211 | } 212 | ], 213 | "start_ms" : 2336, 214 | "return_value" : { 215 | "trace_object" : "value", 216 | "type" : "void", 217 | "object_type" : "not_an_object" 218 | } 219 | }, 220 | { 221 | "method" : "isDebugLogEnabled", 222 | "trace_object" : "call", 223 | "arguments" : [ 224 | 225 | ], 226 | "start_ms" : 2336, 227 | "return_value" : { 228 | "type" : "char", 229 | "trace_object" : "value", 230 | "object_value" : 0, 231 | "object_type" : "not_an_object" 232 | } 233 | }, 234 | { 235 | "method" : "isDebugLogEnabled", 236 | "trace_object" : "call", 237 | "arguments" : [ 238 | 239 | ], 240 | "start_ms" : 3186, 241 | "return_value" : { 242 | "type" : "char", 243 | "trace_object" : "value", 244 | "object_value" : 0, 245 | "object_type" : "not_an_object" 246 | } 247 | }, 248 | { 249 | "method" : "isDebugLogEnabled", 250 | "trace_object" : "call", 251 | "arguments" : [ 252 | 253 | ], 254 | "start_ms" : 3186, 255 | "return_value" : { 256 | "type" : "char", 257 | "trace_object" : "value", 258 | "object_value" : 0, 259 | "object_type" : "not_an_object" 260 | } 261 | }, 262 | { 263 | "method" : "getDeviceInfo", 264 | "trace_object" : "call", 265 | "arguments" : [ 266 | 267 | ], 268 | "start_ms" : 3220, 269 | "return_value" : { 270 | "trace_object" : "value", 271 | "type" : "void", 272 | "object_type" : "not_an_object" 273 | } 274 | }, 275 | { 276 | "method" : "isDebugLogEnabled", 277 | "trace_object" : "call", 278 | "arguments" : [ 279 | 280 | ], 281 | "start_ms" : 3221, 282 | "return_value" : { 283 | "type" : "char", 284 | "trace_object" : "value", 285 | "object_value" : 0, 286 | "object_type" : "not_an_object" 287 | } 288 | }, 289 | { 290 | "method" : "isDebugLogEnabled", 291 | "trace_object" : "call", 292 | "arguments" : [ 293 | 294 | ], 295 | "start_ms" : 3625, 296 | "return_value" : { 297 | "type" : "char", 298 | "trace_object" : "value", 299 | "object_value" : 0, 300 | "object_type" : "not_an_object" 301 | } 302 | }, 303 | { 304 | "method" : "isDebugLogEnabled", 305 | "trace_object" : "call", 306 | "arguments" : [ 307 | 308 | ], 309 | "start_ms" : 3626, 310 | "return_value" : { 311 | "type" : "char", 312 | "trace_object" : "value", 313 | "object_value" : 0, 314 | "object_type" : "not_an_object" 315 | } 316 | } 317 | ] 318 | } 319 | -------------------------------------------------------------------------------- /tracer-objc.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'tracer-objc' 3 | s.version = '0.1' 4 | s.summary = 'Generic record & playback framework for Objective-C' 5 | s.homepage = 'https://github.com/stripe/tracer-objc' 6 | s.authors = { 'Stripe' => 'bg@stripe.com' } 7 | s.frameworks = 'Foundation', 'UIKit' 8 | s.requires_arc = true 9 | s.platform = :ios 10 | s.ios.deployment_target = '9.0' 11 | s.public_header_files = 'Tracer/*.h' 12 | s.source_files = 'Tracer/*.{h,m}', 'Tracer/Private/*.{h,m}' 13 | s.source = { :git => 'https://github.com/stripe/tracer-objc.git', :tag => "v#{s.version}" } 14 | s.license = { :type => 'MIT', :text => <<-LICENSE 15 | The MIT License 16 | 17 | Copyright (c) 2011- Stripe, Inc. (https://stripe.com) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | LICENSE 37 | } 38 | end 39 | --------------------------------------------------------------------------------