Skip to content

Commit 554956a

Browse files
committed
feat: add App/AppWithState interface
1 parent 2b4a7fe commit 554956a

23 files changed

+379
-224
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ Bugsnag Notifiers on other platforms.
88

99
## Enhancements
1010

11+
* Convert `event.app` from `NSDictionary` to a structured class
12+
[#520](https://github.com/bugsnag/bugsnag-cocoa/pull/520)
13+
1114
* Make `BugsnagClient` a public interface
1215
[#517](https://github.com/bugsnag/bugsnag-cocoa/pull/517)
1316

OSX/Bugsnag.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@
178178
E79E6BDA1F4E3850002B35F9 /* BSG_RFC3339DateTool.m in Sources */ = {isa = PBXBuildFile; fileRef = E79E6B6E1F4E3850002B35F9 /* BSG_RFC3339DateTool.m */; };
179179
E79E6BDB1F4E3850002B35F9 /* BSG_KSCrashReportFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = E79E6B711F4E3850002B35F9 /* BSG_KSCrashReportFilter.h */; };
180180
E79E6BDC1F4E3850002B35F9 /* BSG_KSCrashReportFilterCompletion.h in Headers */ = {isa = PBXBuildFile; fileRef = E79E6B721F4E3850002B35F9 /* BSG_KSCrashReportFilterCompletion.h */; };
181+
E7A9E56924361B6300D99F8A /* BugsnagAppTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */; };
181182
E7AB4B9E2423E184004F015A /* BugsnagOnBreadcrumbTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */; };
182183
E7CE78BB1FD94E77001D07E0 /* KSCrashReportConverter_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7CE78991FD94E60001D07E0 /* KSCrashReportConverter_Tests.m */; };
183184
E7CE78BC1FD94E77001D07E0 /* KSCrashReportStore_Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E7CE78951FD94E5F001D07E0 /* KSCrashReportStore_Tests.m */; };
@@ -289,6 +290,7 @@
289290
E790C46D24349CE2006FFB26 /* BugsnagAppWithState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagAppWithState.m; path = ../Source/BugsnagAppWithState.m; sourceTree = "<group>"; };
290291
E790C46E24349CE2006FFB26 /* BugsnagThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BugsnagThread.h; path = ../Source/BugsnagThread.h; sourceTree = "<group>"; };
291292
E790C46F24349CE2006FFB26 /* BugsnagThread.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BugsnagThread.m; path = ../Source/BugsnagThread.m; sourceTree = "<group>"; };
293+
E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagAppTest.m; sourceTree = "<group>"; };
292294
E791482B1FD82B0C003EFEBF /* BugsnagSessionTrackerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTrackerTest.m; sourceTree = "<group>"; };
293295
E791482C1FD82B0C003EFEBF /* BugsnagSessionTrackingPayloadTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTrackingPayloadTest.m; sourceTree = "<group>"; };
294296
E791482D1FD82B0C003EFEBF /* BugsnagSessionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BugsnagSessionTest.m; sourceTree = "<group>"; };
@@ -552,6 +554,7 @@
552554
8A2C8FAF1C6BC1F700846019 /* Tests */ = {
553555
isa = PBXGroup;
554556
children = (
557+
E790C4A22434CB6A006FFB26 /* BugsnagAppTest.m */,
555558
E790C41F2432314A006FFB26 /* BugsnagClientMirrorTest.m */,
556559
E7AB4B9D2423E184004F015A /* BugsnagOnBreadcrumbTest.m */,
557560
00F9393B23FD2D9B008C7073 /* BugsnagTestsDummyClass.h */,
@@ -1035,6 +1038,7 @@
10351038
buildActionMask = 2147483647;
10361039
files = (
10371040
E7CE78CC1FD94E77001D07E0 /* KSSystemInfo_Tests.m in Sources */,
1041+
E7A9E56924361B6300D99F8A /* BugsnagAppTest.m in Sources */,
10381042
4B775FD422CBE02F004839C5 /* BugsnagCollectionsBSGDictInsertIfNotNilTest.m in Sources */,
10391043
E7CE78C91FD94E77001D07E0 /* KSSignalInfo_Tests.m in Sources */,
10401044
E7CE78C61FD94E77001D07E0 /* KSMach_Tests.m in Sources */,

Source/BugsnagApp.h

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,45 @@
88

99
#import <Foundation/Foundation.h>
1010

11-
NS_ASSUME_NONNULL_BEGIN
12-
1311
/**
1412
* Stateless information set by the notifier about your app can be found on this class. These values
1513
* can be accessed and amended if necessary.
1614
*/
1715
@interface BugsnagApp : NSObject
1816

19-
@end
17+
/**
18+
* The bundle version used by the application
19+
*/
20+
@property(nonatomic) NSString *_Nullable bundleVersion;
21+
22+
/**
23+
* The revision ID from the manifest (React Native apps only)
24+
*/
25+
@property(nonatomic) NSString *_Nullable codeBundleId;
26+
27+
/**
28+
* Unique identifier for the debug symbols file corresponding to the application
29+
*/
30+
@property(nonatomic) NSString *_Nullable dsymUuid;
2031

21-
NS_ASSUME_NONNULL_END
32+
/**
33+
* The app identifier used by the application
34+
*/
35+
@property(nonatomic) NSString *_Nullable id;
36+
37+
/**
38+
* The release stage set in Configuration
39+
*/
40+
@property(nonatomic) NSString *_Nullable releaseStage;
41+
42+
/**
43+
* The application type set in Configuration
44+
*/
45+
@property(nonatomic) NSString *_Nullable type;
46+
47+
/**
48+
* The version of the application set in Configuration
49+
*/
50+
@property(nonatomic) NSString *_Nullable version;
51+
52+
@end

Source/BugsnagApp.m

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,42 @@
77
//
88

99
#import "BugsnagApp.h"
10+
#import "BugsnagKeys.h"
11+
#import "BugsnagConfiguration.h"
12+
#import "BugsnagCollections.h"
1013

1114
@implementation BugsnagApp
1215

16+
+ (BugsnagApp *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config {
17+
BugsnagApp *app = [BugsnagApp new];
18+
[self populateFields:app dictionary:event config:config];
19+
return app;
20+
}
21+
22+
+ (void)populateFields:(BugsnagApp *)app dictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config {
23+
NSDictionary *system = event[BSGKeySystem];
24+
app.id = system[@"CFBundleIdentifier"];
25+
app.bundleVersion = system[@"CFBundleVersion"];
26+
app.dsymUuid = system[@"app_uuid"];
27+
app.version = [event valueForKeyPath:@"user.config.appVersion"] ?: system[@"CFBundleShortVersionString"];
28+
app.releaseStage = [event valueForKeyPath:@"user.config.releaseStage"] ?: config.releaseStage;
29+
app.codeBundleId = config.codeBundleId;
30+
app.type = config.appType;
31+
}
32+
33+
- (NSDictionary *)toDict {
34+
NSMutableDictionary *dict = [NSMutableDictionary new];
35+
BSGDictInsertIfNotNil(dict, self.bundleVersion, @"bundleVersion");
36+
BSGDictInsertIfNotNil(dict, self.codeBundleId, @"codeBundleId");
37+
BSGDictInsertIfNotNil(dict, self.id, @"id");
38+
BSGDictInsertIfNotNil(dict, self.releaseStage, @"releaseStage");
39+
BSGDictInsertIfNotNil(dict, self.type, @"type");
40+
BSGDictInsertIfNotNil(dict, self.version, @"version");
41+
42+
if (self.dsymUuid != nil) {
43+
BSGDictInsertIfNotNil(dict, @[self.dsymUuid], @"dsymUUIDs");
44+
}
45+
return dict;
46+
}
47+
1348
@end

Source/BugsnagAppWithState.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,25 @@
1010

1111
#import "BugsnagApp.h"
1212

13-
NS_ASSUME_NONNULL_BEGIN
14-
1513
/**
1614
* Stateful information set by the notifier about your app can be found on this class. These values
1715
* can be accessed and amended if necessary.
1816
*/
1917
@interface BugsnagAppWithState : BugsnagApp
2018

21-
@end
19+
/**
20+
* The number of milliseconds the application was running before the event occurred
21+
*/
22+
@property(nonatomic) NSUInteger duration;
23+
24+
/**
25+
* The number of milliseconds the application was running in the foreground before the
26+
* event occurred
27+
*/
28+
@property(nonatomic) NSUInteger durationInForeground;
2229

23-
NS_ASSUME_NONNULL_END
30+
/**
31+
* Whether the application was in the foreground when the event occurred
32+
*/
33+
@property(nonatomic) BOOL inForeground;
34+
@end

Source/BugsnagAppWithState.m

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,52 @@
77
//
88

99
#import "BugsnagAppWithState.h"
10+
#import "BugsnagKeys.h"
11+
#import "BugsnagConfiguration.h"
12+
#import "BugsnagCollections.h"
13+
14+
@interface BugsnagApp ()
15+
+ (void)populateFields:(BugsnagApp *)app dictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config;
16+
17+
- (NSDictionary *)toDict;
18+
@end
1019

1120
@implementation BugsnagAppWithState
1221

22+
+ (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event {
23+
BugsnagAppWithState *app = [BugsnagAppWithState new];
24+
app.id = event[@"id"];
25+
app.releaseStage = event[@"releaseStage"];
26+
app.version = event[@"version"];
27+
app.bundleVersion = event[@"bundleVersion"];
28+
app.codeBundleId = event[@"codeBundleId"];
29+
app.inForeground = [event[@"inForeground"] boolValue];
30+
app.type = event[@"type"];
31+
return app;
32+
}
33+
34+
+ (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config {
35+
BugsnagAppWithState *app = [BugsnagAppWithState new];
36+
NSDictionary *system = event[BSGKeySystem];
37+
NSDictionary *stats = system[@"application_stats"];
38+
39+
// convert from seconds to milliseconds
40+
NSUInteger activeTimeSinceLaunch = (NSUInteger) ([stats[@"active_time_since_launch"] longValue] * 1000);
41+
NSUInteger backgroundTimeSinceLaunch = (NSUInteger) ([stats[@"background_time_since_launch"] longValue] * 1000);
42+
43+
app.durationInForeground = activeTimeSinceLaunch;
44+
app.duration = activeTimeSinceLaunch + backgroundTimeSinceLaunch;
45+
app.inForeground = [stats[@"application_in_foreground"] boolValue];
46+
[BugsnagApp populateFields:app dictionary:event config:config];
47+
return app;
48+
}
49+
50+
- (NSDictionary *)toDict {
51+
NSMutableDictionary *dict = (NSMutableDictionary *) [super toDict];
52+
BSGDictInsertIfNotNil(dict, @(self.duration), @"duration");
53+
BSGDictInsertIfNotNil(dict, @(self.durationInForeground), @"durationInForeground");
54+
BSGDictInsertIfNotNil(dict, @(self.inForeground), @"inForeground");
55+
return dict;
56+
}
57+
1358
@end

Source/BugsnagConfiguration.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,14 @@ - (instancetype _Nonnull)initWithApiKey:(NSString *_Nonnull)apiKey
169169
#else
170170
_releaseStage = BSGKeyProduction;
171171
#endif
172+
173+
#if TARGET_OS_TV
174+
_appType = @"tvOS";
175+
#elif TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
176+
_appType = @"iOS";
177+
#elif TARGET_OS_MAC
178+
_appType = @"macOS";
179+
#endif
172180

173181
return self;
174182
}

Source/BugsnagEvent.h

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@class BugsnagHandledState;
1313
@class BugsnagSession;
1414
@class BugsnagBreadcrumb;
15+
@class BugsnagAppWithState;
1516

1617
typedef NS_ENUM(NSUInteger, BSGSeverity) {
1718
BSGSeverityError,
@@ -148,10 +149,6 @@ initWithErrorName:(NSString *_Nonnull)name
148149
* The severity of the error generating the report
149150
*/
150151
@property(readwrite) BSGSeverity severity;
151-
/**
152-
* The release stage of the application
153-
*/
154-
@property(readwrite, copy, nullable) NSString *releaseStage;
155152
/**
156153
* The class of the error generating the report
157154
*/
@@ -183,19 +180,16 @@ initWithErrorName:(NSString *_Nonnull)name
183180
/**
184181
* Device information such as OS name and version
185182
*/
186-
@property(readwrite, copy, nullable) NSDictionary *device;
183+
@property(readonly, nonnull) NSDictionary *device;
187184
/**
188185
* Device state such as memory allocation at crash time
189186
*/
190187
@property(readwrite, copy, nullable) NSDictionary *deviceState;
188+
191189
/**
192190
* App information such as the name, version, and bundle ID
193191
*/
194-
@property(readwrite, copy, nullable) NSDictionary *app;
195-
/**
196-
* Device state such as oreground status and run duration
197-
*/
198-
@property(readwrite, copy, nullable) NSDictionary *appState;
192+
@property(readonly, nonnull) BugsnagAppWithState *app;
199193

200194
/**
201195
* Whether the event was a crash (i.e. unhandled) or handled error in which the system

Source/BugsnagEvent.m

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
#import "Private.h"
2727
#import "BugsnagKeys.h"
2828

29+
@interface BugsnagAppWithState ()
30+
+ (BugsnagAppWithState *)appWithDictionary:(NSDictionary *)event config:(BugsnagConfiguration *)config;
31+
- (NSDictionary *)toDict;
32+
+ (BugsnagAppWithState *)appWithOomData:(NSDictionary *)event;
33+
@end
34+
2935
@interface BugsnagBreadcrumb ()
3036
+ (instancetype _Nullable)breadcrumbWithBlock:
3137
(BSGBreadcrumbConfiguration _Nonnull)block;
@@ -232,10 +238,6 @@ @interface BugsnagEvent ()
232238
* The type of the error, such as `mach` or `user`
233239
*/
234240
@property(nonatomic, readwrite, copy, nullable) NSString *errorType;
235-
/**
236-
* The UUID of the dSYM file
237-
*/
238-
@property(nonatomic, readonly, copy, nullable) NSString *dsymUUID;
239241
/**
240242
* A unique hash identifying this device for the application or vendor
241243
*/
@@ -289,6 +291,11 @@ - (BOOL)shouldBeSent;
289291
* Raw error data
290292
*/
291293
@property(readwrite, copy, nullable) NSDictionary *error;
294+
295+
/**
296+
* The release stage of the application
297+
*/
298+
@property(readwrite, copy, nullable) NSString *releaseStage;
292299
@end
293300

294301
@implementation BugsnagEvent
@@ -304,13 +311,15 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
304311
}
305312

306313
if (self = [super init]) {
314+
BugsnagConfiguration *config = [Bugsnag configuration];
315+
307316
_error = [report valueForKeyPath:@"crash.error"];
308317
_errorType = _error[BSGKeyType];
309318
if ([[report valueForKeyPath:@"user.state.didOOM"] boolValue]) {
310319
_errorClass = BSGParseErrorClass(_error, _errorType);
311320
_errorMessage = BSGParseErrorMessage(report, _error, _errorType);
312321
_breadcrumbs = BSGParseBreadcrumbs(report);
313-
_app = [report valueForKeyPath:@"user.state.oom.app"];
322+
_app = [BugsnagAppWithState appWithOomData:[report valueForKeyPath:@"user.state.oom.app"]];
314323
_device = [report valueForKeyPath:@"user.state.oom.device"];
315324
_releaseStage = [report valueForKeyPath:@"user.state.oom.app.releaseStage"];
316325
_handledState = [BugsnagHandledState handledStateWithSeverityReason:LikelyOutOfMemory];
@@ -338,18 +347,13 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
338347
}
339348
_binaryImages = report[@"binary_images"];
340349
_breadcrumbs = BSGParseBreadcrumbs(report);
341-
_dsymUUID = [report valueForKeyPath:@"system.app_uuid"];
342350
_deviceAppHash = [report valueForKeyPath:@"system.device_app_hash"];
343351
_metadata =
344352
[report valueForKeyPath:@"user.metaData"] ?: [NSDictionary new];
345353
_context = BSGParseContext(report, _metadata);
346354
_deviceState = BSGParseDeviceState(report);
347355
_device = BSGParseDevice(report);
348-
_app = BSGParseApp(report);
349-
_appState = BSGParseAppState(report[BSGKeySystem],
350-
BSGLoadConfigValue(report, @"appVersion"),
351-
_releaseStage, // Already loaded from config
352-
BSGLoadConfigValue(report, @"codeBundleId"));
356+
_app = [BugsnagAppWithState appWithDictionary:report config:config];
353357
_groupingHash = BSGParseGroupingHash(report, _metadata);
354358
_overrides = [report valueForKeyPath:@"user.overrides"];
355359
_customException = BSGParseCustomException(report, [_errorClass copy],
@@ -397,7 +401,7 @@ - (instancetype)initWithKSReport:(NSDictionary *)report {
397401
_errorMessage = message;
398402
_overrides = [NSDictionary new];
399403
_metadata = metadata ?: [NSDictionary new];
400-
_releaseStage = config.releaseStage;
404+
self.releaseStage = config.releaseStage;
401405
_enabledReleaseStages = config.enabledReleaseStages;
402406
// Set context based on current values. May be nil.
403407
_context = metadata[BSGKeyContext] ?: [[Bugsnag configuration] context];
@@ -655,19 +659,7 @@ - (NSDictionary *)toJson {
655659

656660
NSDictionary *device = BSGDictMerge(self.device, self.deviceState);
657661
BSGDictSetSafeObject(event, device, BSGKeyDevice);
658-
659-
NSMutableDictionary *appObj = [NSMutableDictionary new];
660-
[appObj addEntriesFromDictionary:self.app];
661-
662-
for (NSString *key in self.appState) {
663-
BSGDictInsertIfNotNil(appObj, self.appState[key], key);
664-
}
665-
666-
if (self.dsymUUID) {
667-
BSGDictInsertIfNotNil(appObj, @[self.dsymUUID], @"dsymUUIDs");
668-
}
669-
670-
BSGDictSetSafeObject(event, appObj, BSGKeyApp);
662+
BSGDictSetSafeObject(event, [self.app toDict], BSGKeyApp);
671663

672664
BSGDictSetSafeObject(event, [self context], BSGKeyContext);
673665
BSGDictInsertIfNotNil(event, self.groupingHash, BSGKeyGroupingHash);

Source/BugsnagKSCrashSysInfoParser.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,4 @@
1111
#define PLATFORM_WORD_SIZE sizeof(void*)*8
1212

1313
NSDictionary *_Nonnull BSGParseDevice(NSDictionary *_Nonnull report);
14-
NSDictionary *_Nonnull BSGParseApp(NSDictionary *_Nonnull report);
15-
NSDictionary *_Nonnull BSGParseAppState(NSDictionary *_Nonnull report,
16-
NSString *_Nullable preferredVersion,
17-
NSString *_Nullable releaseStage,
18-
NSString *_Nullable codeBundleId);
1914
NSDictionary *_Nonnull BSGParseDeviceState(NSDictionary *_Nonnull report);

0 commit comments

Comments
 (0)