Skip to content

Commit a4b2c10

Browse files
authored
Merge pull request #1010 from BranchMetrics/SDK-777-event-status-callback
SDK-777 event status callback
2 parents 14f26f1 + a723c92 commit a4b2c10

File tree

10 files changed

+317
-23
lines changed

10 files changed

+317
-23
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//
2+
// BNCCallbackMapTests.m
3+
// Branch-SDK-Tests
4+
//
5+
// Created by Ernest Cho on 2/25/20.
6+
// Copyright © 2020 Branch, Inc. All rights reserved.
7+
//
8+
9+
#import <XCTest/XCTest.h>
10+
#import "BNCCallbackMap.h"
11+
#import "NSError+Branch.h"
12+
13+
// expose private storage object for state checks
14+
@interface BNCCallbackMap()
15+
@property (nonatomic, strong, readwrite) NSMapTable *callbacks;
16+
@end
17+
18+
@interface BNCCallbackMapTests : XCTestCase
19+
20+
@end
21+
22+
@implementation BNCCallbackMapTests
23+
24+
- (void)setUp {
25+
// Put setup code here. This method is called before the invocation of each test method in the class.
26+
}
27+
28+
- (void)tearDown {
29+
// Put teardown code here. This method is called after the invocation of each test method in the class.
30+
}
31+
32+
- (void)testRequestSaveAndCallback {
33+
BNCCallbackMap *map = [BNCCallbackMap new];
34+
35+
__block BOOL successResult = NO;
36+
__block NSError *errorResult = nil;
37+
38+
// store a request and callback block
39+
BNCServerRequest *request = [BNCServerRequest new];
40+
[map storeRequest:request withCompletion:^(BOOL success, NSError * _Nullable error) {
41+
successResult = success;
42+
errorResult = error;
43+
}];
44+
45+
// confirm there's one entry
46+
XCTAssert([map containsRequest:request] != NO);
47+
XCTAssert(map.callbacks.count == 1);
48+
49+
// call callback
50+
[map callCompletionForRequest:request withSuccessStatus:YES error:nil];
51+
52+
// check if variable was updated
53+
XCTAssertTrue(successResult);
54+
XCTAssertNil(errorResult);
55+
}
56+
57+
- (void)testRequestSaveAndCallbackWithError {
58+
BNCCallbackMap *map = [BNCCallbackMap new];
59+
60+
__block BOOL successResult = YES;
61+
__block NSError *errorResult = nil;
62+
63+
// store a request and callback block
64+
BNCServerRequest *request = [BNCServerRequest new];
65+
[map storeRequest:request withCompletion:^(BOOL success, NSError * _Nullable error) {
66+
successResult = success;
67+
errorResult = error;
68+
}];
69+
70+
// confirm there's one entry
71+
XCTAssert([map containsRequest:request] != NO);
72+
XCTAssert(map.callbacks.count == 1);
73+
74+
// call callback
75+
[map callCompletionForRequest:request withSuccessStatus:NO error:[NSError branchErrorWithCode:BNCGeneralError localizedMessage:@"Test Error"]];
76+
77+
// check if variable was updated
78+
XCTAssertFalse(successResult);
79+
XCTAssert([@"Test Error" isEqualToString:errorResult.localizedFailureReason]);
80+
}
81+
82+
- (void)testAttemptCallbackWithUnsavedRequest {
83+
BNCCallbackMap *map = [BNCCallbackMap new];
84+
85+
__block BOOL successResult = NO;
86+
__block NSError *errorResult = nil;
87+
88+
// store a request and callback block
89+
BNCServerRequest *request = [BNCServerRequest new];
90+
[map storeRequest:request withCompletion:^(BOOL success, NSError * _Nullable error) {
91+
successResult = success;
92+
errorResult = error;
93+
}];
94+
95+
// confirm there's one entry
96+
XCTAssert([map containsRequest:request] != NO);
97+
XCTAssert(map.callbacks.count == 1);
98+
99+
// confirm a new request results in no callback
100+
request = [BNCServerRequest new];
101+
XCTAssert([map containsRequest:request] == NO);
102+
[map callCompletionForRequest:request withSuccessStatus:YES error:nil];
103+
104+
// check if variable was updated
105+
XCTAssertFalse(successResult);
106+
XCTAssertNil(errorResult);
107+
}
108+
109+
- (void)testRequestsGetReleasedAutomatically {
110+
BNCCallbackMap *map = [BNCCallbackMap new];
111+
112+
__block int count = 0;
113+
114+
BNCServerRequest *request = [BNCServerRequest new];
115+
[map storeRequest:request withCompletion:^(BOOL success, NSError * _Nullable error) {
116+
count++;
117+
}];
118+
119+
for (int i=0; i<100; i++) {
120+
BNCServerRequest *tmp = [BNCServerRequest new];
121+
[map storeRequest:tmp withCompletion:^(BOOL success, NSError * _Nullable error) {
122+
count++;
123+
}];
124+
}
125+
126+
// confirm there's less than 100 entries. By not retaining the tmp request, they should be getting ARC'd
127+
XCTAssert(map.callbacks.count < 100);
128+
129+
// confirm the one request we held does get a callback
130+
[map callCompletionForRequest:request withSuccessStatus:YES error:nil];
131+
XCTAssert(count == 1);
132+
}
133+
134+
@end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//
2+
// BNCCallbackMap.h
3+
// Branch
4+
//
5+
// Created by Ernest Cho on 2/25/20.
6+
// Copyright © 2020 Branch, Inc. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
#import "BNCServerRequest.h"
11+
12+
NS_ASSUME_NONNULL_BEGIN
13+
14+
@interface BNCCallbackMap : NSObject
15+
16+
+ (instancetype)shared;
17+
18+
- (void)storeRequest:(BNCServerRequest *)request withCompletion:(void (^_Nullable)(BOOL success, NSError * _Nullable error))completion;
19+
20+
- (BOOL)containsRequest:(BNCServerRequest *)request;
21+
22+
- (void)callCompletionForRequest:(BNCServerRequest *)request withSuccessStatus:(BOOL)status error:(nullable NSError *)error;
23+
24+
@end
25+
26+
NS_ASSUME_NONNULL_END
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// BNCCallbackMap.m
3+
// Branch
4+
//
5+
// Created by Ernest Cho on 2/25/20.
6+
// Copyright © 2020 Branch, Inc. All rights reserved.
7+
//
8+
9+
#import "BNCCallbackMap.h"
10+
11+
@interface BNCCallbackMap()
12+
@property (nonatomic, strong, readwrite) NSMapTable *callbacks;
13+
@end
14+
15+
@implementation BNCCallbackMap
16+
17+
+ (instancetype)shared {
18+
static BNCCallbackMap *map;
19+
static dispatch_once_t onceToken;
20+
dispatch_once(&onceToken, ^{
21+
map = [BNCCallbackMap new];
22+
});
23+
return map;
24+
}
25+
26+
- (instancetype)init {
27+
self = [super init];
28+
if (self) {
29+
30+
// the key is a weak pointer to the request object
31+
// the value is a strong pointer to the request callback block
32+
// if the request object becomes nil, the callback block is lost
33+
self.callbacks = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory];
34+
}
35+
return self;
36+
}
37+
38+
- (void)storeRequest:(BNCServerRequest *)request withCompletion:(void (^_Nullable)(BOOL success, NSError * _Nullable error))completion {
39+
[self.callbacks setObject:completion forKey:request];
40+
}
41+
42+
- (BOOL)containsRequest:(BNCServerRequest *)request {
43+
BOOL contains = NO;
44+
if ([self.callbacks objectForKey:request] != nil) {
45+
contains = YES;
46+
}
47+
return contains;
48+
}
49+
50+
- (void)callCompletionForRequest:(BNCServerRequest *)request withSuccessStatus:(BOOL)status error:(nullable NSError *)error {
51+
void (^completion)(BOOL, NSError * _Nullable) = [self.callbacks objectForKey:request];
52+
if (completion) {
53+
completion(status, error);
54+
}
55+
}
56+
57+
@end

Branch-SDK/Branch-SDK/BNCDeviceInfo.m

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@
2929

3030
@interface BNCDeviceInfo()
3131

32-
@property (nonatomic, strong, readwrite) BNCReachability *reachability;
33-
3432
@property (nonatomic, copy, readwrite) NSString *randomId;
3533

3634
@end
@@ -66,9 +64,7 @@ - (void)loadDeviceInfo {
6664
BNCLocale *locale = [BNCLocale new];
6765
BNCTelephony *telephony = [BNCTelephony new];
6866
BNCDeviceSystem *deviceSystem = [BNCDeviceSystem new];
69-
70-
self.reachability = [BNCReachability new];
71-
67+
7268
// The random id is regenerated per app launch. This maintains existing behavior.
7369
self.randomId = [[NSUUID UUID] UUIDString];
7470
self.vendorId = [BNCSystemObserver getVendorId];
@@ -114,7 +110,7 @@ - (NSString *)localIPAddress {
114110
}
115111

116112
- (NSString *)connectionType {
117-
return [self.reachability reachabilityStatus];
113+
return [[BNCReachability shared] reachabilityStatus];
118114
}
119115

120116
- (NSString *)userAgentString {
@@ -160,7 +156,7 @@ - (NSDictionary *)v2dictionary {
160156
[dictionary bnc_safeSetObject:self.vendorId forKey:@"idfv"];
161157
[dictionary bnc_safeSetObject:self.advertiserId forKey:@"idfa"];
162158
}
163-
[dictionary bnc_safeSetObject:[BNCNetworkInterface localIPAddress] forKey:@"local_ip"];
159+
[dictionary bnc_safeSetObject:[self localIPAddress] forKey:@"local_ip"];
164160

165161
if (!self.isAdTrackingEnabled) {
166162
dictionary[@"limit_ad_tracking"] = @(YES);
@@ -184,7 +180,7 @@ - (NSDictionary *)v2dictionary {
184180
[dictionary bnc_safeSetObject:self.language forKey:@"language"];
185181
[dictionary bnc_safeSetObject:self.carrierName forKey:@"device_carrier"];
186182
[dictionary bnc_safeSetObject:[self connectionType] forKey:@"connection_type"];
187-
[dictionary bnc_safeSetObject:[BNCUserAgentCollector instance].userAgent forKey:@"user_agent"];
183+
[dictionary bnc_safeSetObject:[self userAgentString] forKey:@"user_agent"];
188184

189185
[dictionary bnc_safeSetObject:[BNCPreferenceHelper preferenceHelper].userIdentity forKey:@"developer_identity"];
190186
[dictionary bnc_safeSetObject:[BNCPreferenceHelper preferenceHelper].deviceFingerprintID forKey:@"device_fingerprint_id"];

Branch-SDK/Branch-SDK/BNCReachability.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ NS_ASSUME_NONNULL_BEGIN
1313
// Handles network connectivity and type information
1414
@interface BNCReachability : NSObject
1515

16+
+ (BNCReachability *)shared;
17+
1618
- (nullable NSString *)reachabilityStatus;
1719

1820
@end

Branch-SDK/Branch-SDK/BNCReachability.m

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ @interface BNCReachability()
2727
*/
2828
@implementation BNCReachability
2929

30+
+ (BNCReachability *)shared {
31+
static BNCReachability *reachability;
32+
static dispatch_once_t onceToken;
33+
dispatch_once(&onceToken, ^{
34+
reachability = [BNCReachability new];
35+
});
36+
return reachability;
37+
}
38+
3039
- (instancetype)init {
3140
self = [super init];
3241
if (self) {

Branch-SDK/Branch-SDK/Branch.m

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
#import "BNCAppleSearchAds.h"
4444
#import "BNCFacebookAppLinks.h"
4545
#import "BNCDeviceInfo.h"
46+
#import "BNCCallbackMap.h"
4647

4748
NSString * const BRANCH_FEATURE_TAG_SHARE = @"share";
4849
NSString * const BRANCH_FEATURE_TAG_REFERRAL = @"referral";
@@ -1873,7 +1874,12 @@ - (void) processRequest:(BNCServerRequest*)req
18731874
error.code == BNCBadRequestError ||
18741875
error.code == BNCDuplicateResourceError) {
18751876

1876-
BNCPerformBlockOnMainThreadSync(^{ [req processResponse:response error:error]; });
1877+
BNCPerformBlockOnMainThreadSync(^{
1878+
[req processResponse:response error:error];
1879+
if ([req isKindOfClass:[BranchEventRequest class]]) {
1880+
[[BNCCallbackMap shared] callCompletionForRequest:req withSuccessStatus:(error == nil) error:error];
1881+
}
1882+
});
18771883

18781884
[self.requestQueue dequeue];
18791885
self.networkCount = 0;
@@ -1895,16 +1901,8 @@ - (void) processRequest:(BNCServerRequest*)req
18951901
// Next, remove all the requests that should not be replayed. Note, we do this before
18961902
// calling callbacks, in case any of the callbacks try to kick off another request, which
18971903
// could potentially start another request (and call these callbacks again)
1898-
1899-
NSSet<Class> *replayableRequests = [[NSSet alloc] initWithArray:@[
1900-
BranchEventRequest.class,
1901-
BranchUserCompletedActionRequest.class,
1902-
BranchSetIdentityRequest.class,
1903-
BranchCommerceEventRequest.class,
1904-
]];
1905-
19061904
for (BNCServerRequest *request in requestsToFail) {
1907-
if (Branch.trackingDisabled || ![replayableRequests containsObject:request.class]) {
1905+
if (Branch.trackingDisabled || ![self isReplayableRequest:request]) {
19081906
[self.requestQueue remove:request];
19091907
}
19101908
}
@@ -1914,11 +1912,42 @@ - (void) processRequest:(BNCServerRequest*)req
19141912

19151913
// Finally, call all the requests callbacks with the error
19161914
for (BNCServerRequest *request in requestsToFail) {
1917-
BNCPerformBlockOnMainThreadSync(^ { [request processResponse:nil error:error]; });
1915+
BNCPerformBlockOnMainThreadSync(^ {
1916+
[request processResponse:nil error:error];
1917+
1918+
// BranchEventRequests can have callbacks directly tied to them.
1919+
if ([request isKindOfClass:[BranchEventRequest class]]) {
1920+
NSError *error = [NSError branchErrorWithCode:BNCGeneralError localizedMessage:@"Cancelling queued network requests due to a previous network error."];
1921+
[[BNCCallbackMap shared] callCompletionForRequest:req withSuccessStatus:NO error:error];
1922+
}
1923+
});
19181924
}
19191925
}
19201926
}
19211927

1928+
- (BOOL)isReplayableRequest:(BNCServerRequest *)request {
1929+
1930+
// These request types
1931+
NSSet<Class> *replayableRequests = [[NSSet alloc] initWithArray:@[
1932+
BranchEventRequest.class,
1933+
BranchUserCompletedActionRequest.class,
1934+
BranchSetIdentityRequest.class,
1935+
BranchCommerceEventRequest.class,
1936+
]];
1937+
1938+
if ([replayableRequests containsObject:request.class]) {
1939+
1940+
// Check if the client registered a callback for this request.
1941+
// This indicates the client will handle retry themselves, so fail it.
1942+
if ([[BNCCallbackMap shared] containsRequest:request]) {
1943+
return NO;
1944+
} else {
1945+
return YES;
1946+
}
1947+
}
1948+
return NO;
1949+
}
1950+
19221951
- (void)processNextQueueItem {
19231952
dispatch_semaphore_wait(self.processing_sema, DISPATCH_TIME_FOREVER);
19241953

Branch-SDK/Branch-SDK/BranchEvent.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,12 @@ typedef NS_ENUM(NSInteger, BranchEventAdType) {
8888
@property (nonatomic, copy) NSArray<BranchUniversalObject*>*_Nonnull contentItems;
8989
@property (nonatomic, copy) NSDictionary<NSString*, NSString*> *_Nonnull customData;
9090

91-
- (void) logEvent; //!< Logs the event on the Branch server.
91+
/**
92+
Logs the event on the Branch server. This version will callback on success/failure.
93+
*/
94+
- (void)logEventWithCompletion:(void (^_Nullable)(BOOL success, NSError * _Nullable error))completion;
95+
96+
- (void) logEvent; //!< Logs the event on the Branch server. This version automatically caches and retries as necessary.
9297
- (NSDictionary*_Nonnull) dictionary; //!< Returns a dictionary representation of the event.
9398
- (NSString* _Nonnull) description; //!< Returns a string description of the event.
9499
@end

0 commit comments

Comments
 (0)