Skip to content

Commit 20cb973

Browse files
authored
Implement reachability and upload conditions (#2826)
* Add a reachability implementation * Add a reachability private header * Define more useful upload conditions and start at 1 instead of 0 * Implement determining upload conditions * Add hooks for lifecycle events to the CCT implementation. * Style
1 parent e8e88d3 commit 20cb973

File tree

10 files changed

+294
-63
lines changed

10 files changed

+294
-63
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
#import <SystemConfiguration/SCNetworkReachability.h>
20+
21+
NS_ASSUME_NONNULL_BEGIN
22+
23+
/** This class helps determine upload conditions by determining connectivity. */
24+
@interface GDTReachability : NSObject
25+
26+
/** The current set flags indicating network conditions */
27+
+ (SCNetworkReachabilityFlags)currentFlags;
28+
29+
@end
30+
31+
NS_ASSUME_NONNULL_END
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GDTReachability.h"
18+
#import "GDTReachability_Private.h"
19+
20+
#import <netinet/in.h>
21+
22+
/** Sets the _callbackFlag ivar whenever the network changes.
23+
*
24+
* @param reachability The reachability object calling back.
25+
* @param flags The new flag values.
26+
* @param info Any data that might be passed in by the callback.
27+
*/
28+
static void GDTReachabilityCallback(SCNetworkReachabilityRef reachability,
29+
SCNetworkReachabilityFlags flags,
30+
void *info);
31+
32+
@implementation GDTReachability {
33+
/** The reachability object. */
34+
SCNetworkReachabilityRef _reachabilityRef;
35+
36+
/** The queue on which callbacks and all work will occur. */
37+
dispatch_queue_t _reachabilityQueue;
38+
39+
/** Flags specified by reachability callbacks. */
40+
SCNetworkConnectionFlags _callbackFlags;
41+
}
42+
43+
+ (void)load {
44+
[self sharedInstance];
45+
}
46+
47+
+ (instancetype)sharedInstance {
48+
static GDTReachability *sharedInstance;
49+
static dispatch_once_t onceToken;
50+
dispatch_once(&onceToken, ^{
51+
sharedInstance = [[GDTReachability alloc] init];
52+
});
53+
return sharedInstance;
54+
}
55+
56+
+ (SCNetworkReachabilityFlags)currentFlags {
57+
__block SCNetworkReachabilityFlags currentFlags;
58+
dispatch_sync([GDTReachability sharedInstance] -> _reachabilityQueue, ^{
59+
GDTReachability *reachability = [GDTReachability sharedInstance];
60+
currentFlags = reachability->_flags ? reachability->_flags : reachability->_callbackFlags;
61+
});
62+
return currentFlags;
63+
}
64+
65+
- (instancetype)init {
66+
self = [super init];
67+
if (self) {
68+
struct sockaddr_in zeroAddress;
69+
bzero(&zeroAddress, sizeof(zeroAddress));
70+
zeroAddress.sin_len = sizeof(zeroAddress);
71+
zeroAddress.sin_family = AF_INET;
72+
73+
_reachabilityQueue = dispatch_queue_create("com.google.GDTReachability", DISPATCH_QUEUE_SERIAL);
74+
_reachabilityRef = SCNetworkReachabilityCreateWithAddress(
75+
kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress);
76+
Boolean success = SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, _reachabilityQueue);
77+
NSAssert(success, @"The reachability queue wasn't set.");
78+
success = SCNetworkReachabilitySetCallback(_reachabilityRef, GDTReachabilityCallback, NULL);
79+
NSAssert(success, @"The reachability callback wasn't set.");
80+
81+
// Get the initial set of flags.
82+
dispatch_async(_reachabilityQueue, ^{
83+
Boolean valid = SCNetworkReachabilityGetFlags(self->_reachabilityRef, &self->_flags);
84+
if (!valid) {
85+
self->_flags = 0;
86+
}
87+
});
88+
}
89+
return self;
90+
}
91+
92+
- (void)setCallbackFlags:(SCNetworkReachabilityFlags)flags {
93+
if (_callbackFlags != flags) {
94+
self->_callbackFlags = flags;
95+
}
96+
}
97+
98+
@end
99+
100+
static void GDTReachabilityCallback(SCNetworkReachabilityRef reachability,
101+
SCNetworkReachabilityFlags flags,
102+
void *info) {
103+
[[GDTReachability sharedInstance] setCallbackFlags:flags];
104+
}

GoogleDataTransport/GoogleDataTransport/Classes/GDTUploadCoordinator.m

Lines changed: 91 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#import "GDTAssert.h"
2121
#import "GDTClock.h"
2222
#import "GDTConsoleLogger.h"
23+
#import "GDTReachability.h"
2324
#import "GDTRegistrar_Private.h"
2425
#import "GDTStorage.h"
2526
#import "GDTUploadPackage_Private.h"
@@ -85,8 +86,11 @@ - (void)forceUploadForTarget:(GDTTarget)target {
8586
if (self->_runningInBackground) {
8687
[self->_forcedUploadQueue insertObject:forceUploadBlock atIndex:0];
8788
[NSKeyedArchiver archiveRootObject:self toFile:[GDTUploadCoordinator archivePath]];
88-
89-
// Enqueue the force upload block if there's an in-flight upload for that target already.
89+
// Enqueue the force upload block if conditions are bad or if there's an in-flight upload for
90+
// that target already.
91+
} else if (([self uploadConditions] & GDTUploadConditionNoNetwork) ==
92+
GDTUploadConditionNoNetwork) {
93+
[self->_forcedUploadQueue insertObject:forceUploadBlock atIndex:0];
9094
} else if (self->_targetToInFlightEventSet[targetNumber]) {
9195
[self->_forcedUploadQueue insertObject:forceUploadBlock atIndex:0];
9296
} else {
@@ -109,39 +113,34 @@ - (GDTStorage *)storage {
109113
// This should always be called in a thread-safe manner. When running the background, in theory,
110114
// the uploader's background task should be calling this.
111115
- (GDTUploaderCompletionBlock)onCompleteBlock {
112-
__weak GDTUploadCoordinator *weakSelf = self;
113116
static GDTUploaderCompletionBlock onCompleteBlock;
114117
static dispatch_once_t onceToken;
115118
dispatch_once(&onceToken, ^{
116119
onCompleteBlock = ^(GDTTarget target, GDTClock *nextUploadAttemptUTC, NSError *error) {
117-
GDTUploadCoordinator *strongSelf = weakSelf;
118-
if (strongSelf) {
119-
dispatch_async(strongSelf.coordinationQueue, ^{
120-
NSNumber *targetNumber = @(target);
121-
if (error) {
122-
GDTLogWarning(GDTMCWUploadFailed, @"Error during upload: %@", error);
123-
[strongSelf->_targetToInFlightEventSet removeObjectForKey:targetNumber];
124-
return;
125-
}
126-
strongSelf->_targetToNextUploadTimes[targetNumber] = nextUploadAttemptUTC;
127-
NSSet<GDTStoredEvent *> *events =
128-
[strongSelf->_targetToInFlightEventSet objectForKey:targetNumber];
129-
GDTAssert(events, @"There should be an in-flight event set to remove.");
130-
[strongSelf.storage removeEvents:events];
131-
[strongSelf->_targetToInFlightEventSet removeObjectForKey:targetNumber];
132-
if (strongSelf->_runningInBackground) {
133-
[NSKeyedArchiver archiveRootObject:self toFile:[GDTUploadCoordinator archivePath]];
134-
} else if (strongSelf->_forcedUploadQueue.count) {
135-
GDTUploadCoordinatorForceUploadBlock queuedBlock =
136-
[strongSelf->_forcedUploadQueue lastObject];
137-
if (queuedBlock) {
138-
dispatch_async(strongSelf->_coordinationQueue, ^{
139-
queuedBlock();
140-
});
141-
}
120+
dispatch_async(self->_coordinationQueue, ^{
121+
NSNumber *targetNumber = @(target);
122+
if (error) {
123+
GDTLogWarning(GDTMCWUploadFailed, @"Error during upload: %@", error);
124+
[self->_targetToInFlightEventSet removeObjectForKey:targetNumber];
125+
return;
126+
}
127+
self->_targetToNextUploadTimes[targetNumber] = nextUploadAttemptUTC;
128+
NSSet<GDTStoredEvent *> *events =
129+
[self->_targetToInFlightEventSet objectForKey:targetNumber];
130+
GDTAssert(events, @"There should be an in-flight event set to remove.");
131+
[self->_storage removeEvents:events];
132+
[self->_targetToInFlightEventSet removeObjectForKey:targetNumber];
133+
if (self->_runningInBackground) {
134+
[NSKeyedArchiver archiveRootObject:self toFile:[GDTUploadCoordinator archivePath]];
135+
} else if (self->_forcedUploadQueue.count) {
136+
GDTUploadCoordinatorForceUploadBlock queuedBlock = [self->_forcedUploadQueue lastObject];
137+
if (queuedBlock) {
138+
dispatch_async(self->_coordinationQueue, ^{
139+
queuedBlock();
140+
});
142141
}
143-
});
144-
}
142+
}
143+
});
145144
};
146145
});
147146
return onCompleteBlock;
@@ -153,20 +152,17 @@ - (GDTUploaderCompletionBlock)onCompleteBlock {
153152
* check the next-upload clocks of all targets to determine if an upload attempt can be made.
154153
*/
155154
- (void)startTimer {
156-
__weak GDTUploadCoordinator *weakSelf = self;
157155
dispatch_sync(_coordinationQueue, ^{
158-
GDTUploadCoordinator *strongSelf = weakSelf;
159-
GDTAssert(strongSelf, @"self must be real to start a timer.");
160-
strongSelf->_timer =
161-
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, strongSelf->_coordinationQueue);
162-
dispatch_source_set_timer(strongSelf->_timer, DISPATCH_TIME_NOW, strongSelf->_timerInterval,
163-
strongSelf->_timerLeeway);
164-
dispatch_source_set_event_handler(strongSelf->_timer, ^{
165-
if (!strongSelf->_runningInBackground) {
166-
[strongSelf checkPrioritizersAndUploadEvents];
156+
self->_timer =
157+
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_coordinationQueue);
158+
dispatch_source_set_timer(self->_timer, DISPATCH_TIME_NOW, self->_timerInterval,
159+
self->_timerLeeway);
160+
dispatch_source_set_event_handler(self->_timer, ^{
161+
if (!self->_runningInBackground) {
162+
[self checkPrioritizersAndUploadEvents];
167163
}
168164
});
169-
dispatch_resume(strongSelf->_timer);
165+
dispatch_resume(self->_timer);
170166
});
171167
}
172168

@@ -181,36 +177,72 @@ - (void)stopTimer {
181177
* events for that target or not. If so, queries the prioritizers
182178
*/
183179
- (void)checkPrioritizersAndUploadEvents {
184-
__weak GDTUploadCoordinator *weakSelf = self;
185180
dispatch_async(_coordinationQueue, ^{
186181
if (self->_runningInBackground) {
187182
return;
188183
}
184+
185+
GDTUploadConditions conds = [self uploadConditions];
186+
if ((conds & GDTUploadConditionNoNetwork) == GDTUploadConditionNoNetwork) {
187+
return;
188+
}
189+
189190
static int count = 0;
190191
count++;
191-
GDTUploadCoordinator *strongSelf = weakSelf;
192-
if (strongSelf) {
193-
NSArray<NSNumber *> *targetsReadyForUpload = [self targetsReadyForUpload];
194-
for (NSNumber *target in targetsReadyForUpload) {
195-
id<GDTPrioritizer> prioritizer = strongSelf->_registrar.targetToPrioritizer[target];
196-
id<GDTUploader> uploader = strongSelf->_registrar.targetToUploader[target];
197-
GDTAssert(prioritizer && uploader, @"Target '%@' is missing an implementation", target);
198-
GDTUploadConditions conds = [self uploadConditions];
199-
GDTUploadPackage *package = [[prioritizer uploadPackageWithConditions:conds] copy];
200-
package.storage = strongSelf.storage;
201-
if (package.events && package.events.count > 0) {
202-
strongSelf->_targetToInFlightEventSet[target] = package.events;
203-
[uploader uploadPackage:package onComplete:self.onCompleteBlock];
204-
}
192+
NSArray<NSNumber *> *targetsReadyForUpload = [self targetsReadyForUpload];
193+
for (NSNumber *target in targetsReadyForUpload) {
194+
id<GDTPrioritizer> prioritizer = self->_registrar.targetToPrioritizer[target];
195+
id<GDTUploader> uploader = self->_registrar.targetToUploader[target];
196+
GDTAssert(prioritizer && uploader, @"Target '%@' is missing an implementation", target);
197+
GDTUploadPackage *package = [[prioritizer uploadPackageWithConditions:conds] copy];
198+
package.storage = self.storage;
199+
if (package.events && package.events.count > 0) {
200+
self->_targetToInFlightEventSet[target] = package.events;
201+
[uploader uploadPackage:package onComplete:self.onCompleteBlock];
205202
}
206203
}
207204
});
208205
}
209206

210-
/** */
207+
/** Returns the current upload conditions after making determinations about the network connection.
208+
*
209+
* @return The current upload conditions.
210+
*/
211211
- (GDTUploadConditions)uploadConditions {
212-
// TODO: Compute the real upload conditions.
213-
return GDTUploadConditionMobileData;
212+
SCNetworkReachabilityFlags currentFlags = [GDTReachability currentFlags];
213+
214+
BOOL reachable =
215+
(currentFlags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable;
216+
BOOL connectionRequired = (currentFlags & kSCNetworkReachabilityFlagsConnectionRequired) ==
217+
kSCNetworkReachabilityFlagsConnectionRequired;
218+
BOOL interventionRequired = (currentFlags & kSCNetworkReachabilityFlagsInterventionRequired) ==
219+
kSCNetworkReachabilityFlagsInterventionRequired;
220+
BOOL connectionOnDemand = (currentFlags & kSCNetworkReachabilityFlagsConnectionOnDemand) ==
221+
kSCNetworkReachabilityFlagsConnectionOnDemand;
222+
BOOL connectionOnTraffic = (currentFlags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ==
223+
kSCNetworkReachabilityFlagsConnectionOnTraffic;
224+
BOOL isWWAN =
225+
(currentFlags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN;
226+
227+
if (!reachable) {
228+
return GDTUploadConditionNoNetwork;
229+
}
230+
231+
GDTUploadConditions conditions = 0;
232+
conditions |= !connectionRequired ? GDTUploadConditionWifiData : conditions;
233+
conditions |= isWWAN ? GDTUploadConditionMobileData : conditions;
234+
if ((connectionOnTraffic || connectionOnDemand) && !interventionRequired) {
235+
conditions = GDTUploadConditionWifiData;
236+
}
237+
238+
BOOL wifi = (conditions & GDTUploadConditionWifiData) == GDTUploadConditionWifiData;
239+
BOOL cell = (conditions & GDTUploadConditionMobileData) == GDTUploadConditionMobileData;
240+
241+
if (!(wifi || cell)) {
242+
conditions = GDTUploadConditionUnclearConnection;
243+
}
244+
245+
return conditions;
214246
}
215247

216248
/** Checks the next upload time for each target and returns an array of targets that are
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GDTReachability.h"
18+
19+
@interface GDTReachability ()
20+
21+
/** Allows manually setting the flags for testing purposes. */
22+
@property(nonatomic, readwrite) SCNetworkReachabilityFlags flags;
23+
24+
/** Creates/returns the singleton instance of this class.
25+
*
26+
* @return The singleton instance of this class.
27+
*/
28+
+ (instancetype)sharedInstance;
29+
30+
@end

GoogleDataTransport/GoogleDataTransport/Classes/Public/GDTPrioritizer.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,20 @@ NS_ASSUME_NONNULL_BEGIN
2828
*/
2929
typedef NS_OPTIONS(NSInteger, GDTUploadConditions) {
3030

31+
/** An upload shouldn't be attempted, because there's no network. */
32+
GDTUploadConditionNoNetwork = 1 << 0,
33+
3134
/** An upload would likely use mobile data. */
32-
GDTUploadConditionMobileData,
35+
GDTUploadConditionMobileData = 1 << 1,
3336

3437
/** An upload would likely use wifi data. */
35-
GDTUploadConditionWifiData,
38+
GDTUploadConditionWifiData = 1 << 2,
39+
40+
/** An upload uses some sort of network connection, but it's unclear which. */
41+
GDTUploadConditionUnclearConnection = 1 << 3,
3642

3743
/** A high priority event has occurred. */
38-
GDTUploadConditionHighPriority,
44+
GDTUploadConditionHighPriority = 1 << 4,
3945
};
4046

4147
/** This protocol defines the common interface of event prioritization. Prioritizers are

0 commit comments

Comments
 (0)