Skip to content

Commit 5da5e40

Browse files
committed
Multiple calls to sendTag(s) and deleteTag(s) are batched
* This optimizes network calls made from the device.
1 parent 20085ee commit 5da5e40

File tree

4 files changed

+190
-41
lines changed

4 files changed

+190
-41
lines changed
Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,41 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1217" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
3+
<device id="retina4_7" orientation="portrait">
4+
<adaptation id="fullscreen"/>
5+
</device>
36
<dependencies>
4-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
7+
<deployment identifier="iOS"/>
8+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
59
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
610
</dependencies>
711
<scenes>
812
<!--View Controller-->
913
<scene sceneID="tne-QT-ifu">
1014
<objects>
11-
<viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="" sceneMemberID="viewController">
15+
<viewController id="BYZ-38-t0r" customClass="ViewController" sceneMemberID="viewController">
1216
<layoutGuides>
1317
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
1418
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
1519
</layoutGuides>
1620
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
1721
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
1822
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
23+
<subviews>
24+
<button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="H3K-GA-smE">
25+
<rect key="frame" x="139" y="271" width="82" height="30"/>
26+
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
27+
<state key="normal" title="SendTags"/>
28+
<connections>
29+
<action selector="sendTagButton:" destination="BYZ-38-t0r" eventType="touchUpInside" id="bug-x1-TIR"/>
30+
</connections>
31+
</button>
32+
</subviews>
1933
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
2034
</view>
2135
</viewController>
2236
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
2337
</objects>
38+
<point key="canvasLocation" x="117.59999999999999" y="122.78860569715144"/>
2439
</scene>
2540
</scenes>
2641
</document>

iOS_SDK/OneSignalDevApp/OneSignalDevApp/ViewController.m

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030

3131
#import "ViewController.h"
3232

33+
#import <OneSignal/OneSignal.h>
34+
3335
@interface ViewController ()
3436

3537
@end
@@ -41,6 +43,18 @@ - (void)viewDidLoad {
4143
// Do any additional setup after loading the view, typically from a nib.
4244
}
4345

46+
- (IBAction)sendTagButton:(id)sender {
47+
[OneSignal sendTag:@"key1"
48+
value:@"value1"
49+
onSuccess:^(NSDictionary *result) {
50+
static int successes = 0;
51+
NSLog(@"successes: %d", ++successes);
52+
}
53+
onFailure:^(NSError *error) {
54+
static int failures = 0;
55+
NSLog(@"failures: %d", ++failures);
56+
}];
57+
}
4458

4559
- (void)didReceiveMemoryWarning {
4660
[super didReceiveMemoryWarning];

iOS_SDK/OneSignalSDK/Source/OneSignal.m

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,22 @@ @interface OSSubcscriptionStatus : NSObject
9393
@implementation OSSubcscriptionStatus
9494
@end
9595

96+
@interface OSPendingCallbacks : NSObject
97+
@property OSResultSuccessBlock successBlock;
98+
@property OSFailureBlock failureBlock;
99+
@end
100+
101+
@implementation OSPendingCallbacks
102+
@end
103+
96104
@implementation OneSignal
97105

98106
NSString* const ONESIGNAL_VERSION = @"020400";
99107
static NSString* mSDKType = @"native";
100108
static BOOL coldStartFromTapOnNotification = NO;
101109

110+
static NSMutableArray* pendingSendTagCallbacks;
111+
102112
// Has attempted to register for push notifications with Apple since app was installed.
103113
static BOOL registeredWithApple = NO;
104114

@@ -436,28 +446,65 @@ + (void)sendTags:(NSDictionary*)keyValuePair {
436446

437447
+ (void)sendTags:(NSDictionary*)keyValuePair onSuccess:(OSResultSuccessBlock)successBlock onFailure:(OSFailureBlock)failureBlock {
438448

439-
if (mUserId == nil) {
440-
if (tagsToSend == nil)
441-
tagsToSend = [keyValuePair mutableCopy];
442-
else
443-
[tagsToSend addEntriesFromDictionary:keyValuePair];
444-
return;
449+
if (tagsToSend == nil)
450+
tagsToSend = [keyValuePair mutableCopy];
451+
else
452+
[tagsToSend addEntriesFromDictionary:keyValuePair];
453+
454+
if (successBlock || failureBlock) {
455+
if (!pendingSendTagCallbacks)
456+
pendingSendTagCallbacks = [[NSMutableArray alloc] init];
457+
OSPendingCallbacks* pendingCallbacks = [OSPendingCallbacks alloc];
458+
pendingCallbacks.successBlock = successBlock;
459+
pendingCallbacks.failureBlock = failureBlock;
460+
[pendingSendTagCallbacks addObject:pendingCallbacks];
445461
}
446462

463+
464+
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(sendTagsToServer) object:nil];
465+
[self performSelector:@selector(sendTagsToServer) withObject:nil afterDelay:5];
466+
}
467+
468+
// Called only with a delay to batch network calls.
469+
+ (void) sendTagsToServer {
470+
if (!tagsToSend)
471+
return;
472+
473+
NSDictionary* nowSendingTags = tagsToSend;
474+
tagsToSend = nil;
475+
476+
NSArray* nowProcessingCallbacks = pendingSendTagCallbacks;
477+
pendingSendTagCallbacks = nil;
478+
479+
447480
NSMutableURLRequest* request = [httpClient requestWithMethod:@"PUT" path:[NSString stringWithFormat:@"players/%@", mUserId]];
448481

449482
NSDictionary* dataDic = [NSDictionary dictionaryWithObjectsAndKeys:
450483
app_id, @"app_id",
451-
keyValuePair, @"tags",
484+
nowSendingTags, @"tags",
452485
[OneSignalHelper getNetType], @"net_type",
453486
nil];
454487

455488
NSData* postData = [NSJSONSerialization dataWithJSONObject:dataDic options:0 error:nil];
456489
[request setHTTPBody:postData];
457490

458491
[OneSignalHelper enqueueRequest:request
459-
onSuccess:successBlock
460-
onFailure:failureBlock];
492+
onSuccess:^(NSDictionary *result) {
493+
if (nowProcessingCallbacks) {
494+
for(OSPendingCallbacks* callbackSet in nowProcessingCallbacks) {
495+
if (callbackSet.successBlock)
496+
callbackSet.successBlock(result);
497+
}
498+
}
499+
}
500+
onFailure:^(NSError *error) {
501+
if (nowProcessingCallbacks) {
502+
for(OSPendingCallbacks* callbackSet in nowProcessingCallbacks) {
503+
if (callbackSet.failureBlock)
504+
callbackSet.failureBlock(error);
505+
}
506+
}
507+
}];
461508
}
462509

463510
+ (void)sendTag:(NSString*)key value:(NSString*)value {
@@ -494,29 +541,6 @@ + (void)deleteTag:(NSString*)key {
494541
[self deleteTags:@[key] onSuccess:nil onFailure:nil];
495542
}
496543

497-
+ (void)deleteTags:(NSArray*)keys onSuccess:(OSResultSuccessBlock)successBlock onFailure:(OSFailureBlock)failureBlock {
498-
499-
if (mUserId == nil)
500-
return;
501-
502-
NSMutableURLRequest* request;
503-
request = [httpClient requestWithMethod:@"PUT" path:[NSString stringWithFormat:@"players/%@", mUserId]];
504-
505-
NSMutableDictionary* deleteTagsDict = [NSMutableDictionary dictionary];
506-
for(id key in keys)
507-
[deleteTagsDict setObject:@"" forKey:key];
508-
509-
NSDictionary* dataDic = [NSDictionary dictionaryWithObjectsAndKeys:
510-
app_id, @"app_id",
511-
deleteTagsDict, @"tags",
512-
nil];
513-
514-
NSData* postData = [NSJSONSerialization dataWithJSONObject:dataDic options:0 error:nil];
515-
[request setHTTPBody:postData];
516-
517-
[OneSignalHelper enqueueRequest:request onSuccess:successBlock onFailure:failureBlock];
518-
}
519-
520544
+ (void)deleteTags:(NSArray*)keys {
521545
[self deleteTags:keys onSuccess:nil onFailure:nil];
522546
}
@@ -534,6 +558,21 @@ + (void)deleteTagsWithJsonString:(NSString*)jsonString {
534558
}
535559
}
536560

561+
+ (void)deleteTags:(NSArray*)keys onSuccess:(OSResultSuccessBlock)successBlock onFailure:(OSFailureBlock)failureBlock {
562+
NSMutableDictionary* tags = [[NSMutableDictionary alloc] init];
563+
564+
for(NSString* key in keys) {
565+
if (tagsToSend && tagsToSend[key]) {
566+
if (![tagsToSend[key] isEqualToString:@""])
567+
[tagsToSend removeObjectForKey:key];
568+
}
569+
else
570+
tags[key] = @"";
571+
}
572+
573+
[self sendTags:tags onSuccess:successBlock onFailure:failureBlock];
574+
}
575+
537576

538577
+ (void)postNotification:(NSDictionary*)jsonData {
539578
[self postNotification:jsonData onSuccess:nil onFailure:nil];

iOS_SDK/OneSignalSDK/UnitTests/UnitTests.m

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,51 @@ BOOL injectStaticSelector(Class newClass, SEL newSel, Class addToClass, SEL make
7272

7373
// START - Selector Shadowing
7474

75+
76+
@interface SelectorToRun : NSObject
77+
@property NSObject* runOn;
78+
@property SEL selector;
79+
@property NSObject* withObject;
80+
@end
81+
82+
83+
@implementation SelectorToRun
84+
@end
85+
7586
@interface NSObjectOverrider : NSObject
7687
@end
88+
7789
@implementation NSObjectOverrider
7890

79-
static BOOL enabledPerformSelectorAfterDelay = true;
91+
static NSMutableArray* selectorsToRun;
92+
static BOOL instantRunPerformSelectorAfterDelay;
8093

8194
+ (void)load {
8295
injectToProperClass(@selector(overridePerformSelector:withObject:afterDelay:), @selector(performSelector:withObject:afterDelay:), @[], [NSObjectOverrider class], [NSObject class]);
96+
injectToProperClass(@selector(overridePerformSelector:withObject:), @selector(performSelector:withObject:), @[], [NSObjectOverrider class], [NSObject class]);
8397
}
8498

8599
- (void)overridePerformSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay {
86-
if (enabledPerformSelectorAfterDelay)
87-
[self overridePerformSelector:aSelector withObject:nil afterDelay:delay];
100+
if (instantRunPerformSelectorAfterDelay)
101+
[self performSelector:aSelector withObject:anArgument];
102+
else {
103+
SelectorToRun* selectorToRun = [SelectorToRun alloc];
104+
selectorToRun.runOn = self;
105+
selectorToRun.selector = aSelector;
106+
selectorToRun.withObject = anArgument;
107+
[selectorsToRun addObject:selectorToRun];
108+
}
109+
}
110+
111+
- (void)overridePerformSelector:(SEL)aSelector withObject:(nullable id)anArgument {
112+
[self overridePerformSelector:aSelector withObject:anArgument];
113+
}
114+
115+
+ (void)runPendingSelectors {
116+
for(SelectorToRun* selectorToRun in selectorsToRun)
117+
[selectorToRun.runOn performSelector:selectorToRun.selector withObject:selectorToRun.withObject];
118+
119+
[selectorsToRun removeAllObjects];
88120
}
89121

90122
@end
@@ -363,6 +395,9 @@ - (void)setUp {
363395
didFailRegistarationErrorCode = 0;
364396
nsbundleDictionary = @{@"UIBackgroundModes": @[@"remote-notification"]};
365397

398+
instantRunPerformSelectorAfterDelay = false;
399+
selectorsToRun = [[NSMutableArray alloc] init];
400+
366401
// TODO: Keep commented out for now, might need this later.
367402
// [OneSignal performSelector:NSSelectorFromString(@"clearStatics")];
368403

@@ -799,16 +834,62 @@ - (void)testRecievedCallbackWithButtons {
799834
- (void)testSendTags {
800835
[self initOneSignal];
801836
[self runBackgroundThreads];
837+
XCTAssertEqual(networkRequestCount, 1);
802838

839+
// Simple test with a sendTag and sendTags call.
803840
[OneSignal sendTag:@"key" value:@"value"];
804-
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key"], @"value");
805-
806841
[OneSignal sendTags:@{@"key1": @"value1", @"key2": @"value2"}];
842+
843+
// Make sure all 3 sets of tags where send in 1 network call.
844+
[NSObjectOverrider runPendingSelectors];
845+
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key"], @"value");
807846
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key1"], @"value1");
808847
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key2"], @"value2");
848+
XCTAssertEqual(networkRequestCount, 2);
849+
850+
851+
// More advanced test with callbacks.
852+
__block BOOL didRunSuccess1, didRunSuccess2, didRunSuccess3;
853+
[OneSignal sendTag:@"key10" value:@"value10" onSuccess:^(NSDictionary *result) {
854+
didRunSuccess1 = true;
855+
} onFailure:^(NSError *error) {}];
856+
[OneSignal sendTags:@{@"key11": @"value11", @"key12": @"value12"} onSuccess:^(NSDictionary *result) {
857+
didRunSuccess2 = true;
858+
} onFailure:^(NSError *error) {}];
809859

810-
// TODO: Optimization: This could be 1 network call if we delay the request in init and sendTags
860+
instantRunPerformSelectorAfterDelay = true;
861+
[OneSignal sendTag:@"key13" value:@"value13" onSuccess:^(NSDictionary *result) {
862+
didRunSuccess3 = true;
863+
} onFailure:^(NSError *error) {}];
864+
865+
[NSObjectOverrider runPendingSelectors];
866+
867+
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key10"], @"value10");
868+
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key11"], @"value11");
869+
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key12"], @"value12");
870+
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key13"], @"value13");
811871
XCTAssertEqual(networkRequestCount, 3);
872+
873+
XCTAssertEqual(didRunSuccess1, true);
874+
XCTAssertEqual(didRunSuccess2, true);
875+
XCTAssertEqual(didRunSuccess3, true);
876+
}
877+
878+
- (void)testDeleteTags {
879+
[self initOneSignal];
880+
[self runBackgroundThreads];
881+
XCTAssertEqual(networkRequestCount, 1);
882+
883+
// send 2 tags and delete 1 before they get sent off.
884+
[OneSignal sendTag:@"key" value:@"value"];
885+
[OneSignal sendTag:@"key2" value:@"value2"];
886+
[OneSignal deleteTag:@"key"];
887+
888+
// Make sure only 1 network call is made and only key2 gets sent.
889+
[NSObjectOverrider runPendingSelectors];
890+
XCTAssertNil(lastHTTPRequset[@"tags"][@"key"]);
891+
XCTAssertEqualObjects(lastHTTPRequset[@"tags"][@"key2"], @"value2");
892+
XCTAssertEqual(networkRequestCount, 2);
812893
}
813894

814895
- (void)testFirstInitWithNotificationsAlreadyDeclined {

0 commit comments

Comments
 (0)