Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always deliver events asynchronously to integrations. #715

Merged
merged 1 commit into from
Sep 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions Analytics/Classes/Integrations/SEGIntegrationsManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -372,14 +372,14 @@ - (void)refreshSettings
if (self.settingsRequest) {
return;
}

self.settingsRequest = [self.httpClient settingsForWriteKey:self.configuration.writeKey completionHandler:^(BOOL success, NSDictionary *settings) {
seg_dispatch_specific_async(_serialQueue, ^{
if (success) {
[self setCachedSettings:settings];
} else {
// Hotfix: If settings request fail, fall back to using just Segment integration
// Won't catch situation where this callback never gets called - that will get addressed separately in regular dev
// Won't catch situation where this callback never gets called - that will get addressed separately in regular dev
[self setCachedSettings:@{
@"integrations": @{
@"Segment.io": @{ @"apiKey": self.configuration.writeKey },
Expand Down Expand Up @@ -425,14 +425,9 @@ - (BOOL)isTrackEvent:(NSString *)event enabledForIntegration:(NSString *)key inP

- (void)forwardSelector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options
{
// If the event has opted in for syncrhonous delivery, this may be called on any thread.
// Only allow one to be delivered at a time.
@synchronized(self)
{
[self.integrations enumerateKeysAndObjectsUsingBlock:^(NSString *key, id<SEGIntegration> integration, BOOL *stop) {
[self invokeIntegration:integration key:key selector:selector arguments:arguments options:options];
}];
}
[self.integrations enumerateKeysAndObjectsUsingBlock:^(NSString *key, id<SEGIntegration> integration, BOOL *stop) {
[self invokeIntegration:integration key:key selector:selector arguments:arguments options:options];
}];
}

- (void)invokeIntegration:(id<SEGIntegration>)integration key:(NSString *)key selector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options
Expand Down Expand Up @@ -495,11 +490,10 @@ - (void)flushMessageQueue

- (void)callIntegrationsWithSelector:(SEL)selector arguments:(NSArray *)arguments options:(NSDictionary *)options sync:(BOOL)sync
{
if (sync && self.initialized) {
[self forwardSelector:selector arguments:arguments options:options];
return;
}

// TODO: Currently we ignore the `sync` argument and queue the event asynchronously.
// For integrations that need events to be on the main thread, they'll have to do so
// manually and hop back on to the main thread.
// Eventually we should figure out a way to handle this in analytics-ios itself.
seg_dispatch_specific_async(_serialQueue, ^{
if (self.initialized) {
[self flushMessageQueue];
Expand Down
26 changes: 13 additions & 13 deletions AnalyticsTests/AnalyticsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,22 @@ class AnalyticsTests: QuickSpec {
var analytics: SEGAnalytics!
var testMiddleware: TestMiddleware!
var testApplication: TestApplication!

beforeEach {
testMiddleware = TestMiddleware()
config.middlewares = [testMiddleware]
testApplication = TestApplication()
config.application = testApplication
config.trackApplicationLifecycleEvents = true

analytics = SEGAnalytics(configuration: config)
analytics.test_integrationsManager()?.test_setCachedSettings(settings: cachedSettings)
}

afterEach {
analytics.reset()
}

it("initialized correctly") {
expect(analytics.configuration.flushAt) == 20
expect(analytics.configuration.writeKey) == "QUI5ydwIGeFFTa1IvCBUhxL9PyW5B0jE"
Expand All @@ -47,30 +47,30 @@ class AnalyticsTests: QuickSpec {
expect(analytics.configuration.shouldUseBluetooth) == false
expect(analytics.getAnonymousId()).toNot(beNil())
}

it("persists anonymousId") {
let analytics2 = SEGAnalytics(configuration: config)
expect(analytics.getAnonymousId()) == analytics2.getAnonymousId()
}

it("persists userId") {
analytics.identify("testUserId1")

let analytics2 = SEGAnalytics(configuration: config)
analytics2.test_integrationsManager()?.test_setCachedSettings(settings: cachedSettings)

expect(analytics.test_integrationsManager()?.test_segmentIntegration()?.test_userId()) == "testUserId1"
expect(analytics2.test_integrationsManager()?.test_segmentIntegration()?.test_userId()) == "testUserId1"
}

it("continues user activity") {
let activity = NSUserActivity(activityType: NSUserActivityTypeBrowsingWeb)
activity.webpageURL = URL(string: "http://www.segment.com")
analytics.continue(activity)
let referrer = analytics.test_integrationsManager()?.test_segmentIntegration()?.test_referrer()
expect(referrer?["url"] as? String) == "http://www.segment.com"
expect(referrer?["url"] as? String).toEventually(equal("http://www.segment.com"))
}

it("clears user data") {
analytics.identify("testUserId1", traits: [ "Test trait key" : "Test trait value"])
analytics.reset()
Expand All @@ -90,22 +90,22 @@ class AnalyticsTests: QuickSpec {
expect(event?.properties?["referring_application"] as? String) == "testApp"
expect(event?.properties?["url"] as? String) == "test://test"
}

it("fires Application Opened during UIApplicationWillEnterForeground") {
testMiddleware.swallowEvent = true
NotificationCenter.default.post(name: .UIApplicationWillEnterForeground, object: testApplication)
let event = testMiddleware.lastContext?.payload as? SEGTrackPayload
expect(event?.event) == "Application Opened"
expect(event?.properties?["from_background"] as? Bool) == true
}

it("flushes when UIApplicationDidEnterBackgroundNotification is fired") {
analytics.track("test")
NotificationCenter.default.post(name: .UIApplicationDidEnterBackground, object: testApplication)
expect(testApplication.backgroundTasks.count).toEventually(equal(1))
expect(testApplication.backgroundTasks[0].isEnded).toEventually(beFalse())
}

it("protocol conformance should not interfere with UIApplication interface") {
// In Xcode8/iOS10, UIApplication.h typedefs UIBackgroundTaskIdentifier as NSUInteger,
// whereas Swift has UIBackgroundTaskIdentifier typealiaed to Int.
Expand Down