We are pleased to announce a new release of the Snowplow iOS Tracker. Version 1.3.0 is an important release that introduces Global Contexts, GDPR contexts and support for the Swift Package Manager thanks to the contribution of @vauxhall. Swift Package Manager completes the list of supported dependency managers, which already includes Cocoapods and Carthage.

Read on below for:

  1. Global Contexts
  2. Tracking GDPR basis for processing with the GDPR context
  3. Swift Package Manager support
  4. Deprecated methods
  5. Updates and bug fixes
  6. Documentation
  7. Getting help

1. Global Contexts

We are happy to announce the integration of the Global Contexts feature in the iOS tracker (#357).

1.1 A quick recap on contexts

Contexts are entities tracked across multiple events. They are particularly important as they add value to the event through tracking extra information according to a related schema. The schema will specify the kind of data tracked with the context. Typical contexts are: session, geolocation, page and screen views, device informations, user details.

Some of the contexts are tracked out-of-the-box but it’s possible to add custom context tracking to other domain specific events. There is no limit to the number of contexts attached to the events. Not all contexts suit every event. Some of the contexts only make sense on specific events and it’s up to the developer to handle these cases.

1.2 Introducing Global Contexts

Prior to this release, developers needed to programmatically attach custom contexts to each specific event. The global contexts feature helps the developer by making this operation declarative. The developer can declare which contexts have to be attached to which events and the tracker will take care of attaching the contexts to the events when required as declared by the developer.

  • It makes it easier for developers to implement very rich data tracking with Snowplow.
  • It makes the tracking less error-prone - it is less likely that an event will accidentally be sent without a required context attached.
  • That in turn makes it easier for analysts and developers consuming the data, because they can be confident to expect certain events from particular platforms to always have the required contexts attached.

1.3 Getting started with Global Contexts

As explained above, the global contexts are particularly helpful when the developer wants to associate specific contexts to all the events or a subset of them, rather than adding the contexts manually to each event tracked. This can be done at tracker setup declaring the contexts generator and the suitable subset of events.

// Instance a global contexts generator
SPGlobalContext *globalContext1 = [[SPGlobalContext alloc] initWithStaticContexts:@[[[SPSelfDescribingJson alloc] initWithSchema:@"iglu:com.snowplowanalytics.snowplow/test_sdj/jsonschema/1-0-1" andData:@{@"key": @"value"}]]];

...

SPTracker *tracker = [SPTracker build:^(id<SPTrackerBuilder> builder) {
    [builder setEmitter:emitter];
    ...
    [builder setGlobalContextGenerators:@{
        @"tag1": globalContext1,
        @"tag2": globalContext2,
        ...
    }];
}];

The method setGlobalContextGenerators: can be used to set up the generators which are able to create contexts. Each context generator is associated a tag string. The tag string can be used to remove a generator at runtime using the method removeGlobalContext.

SPGlobalContext *globalContext = [tracker removeGlobalContext:@"tag1"];

It returns nil in case the there aren’t global contexts stored with the specified tag, otherwise it returns the removed SPGlobalContext instance.

It’s possible to add global contexts at runtime with the method addGlobalContext.

BOOL isAdded = [tracker addGlobalContext:globalContext tag:@"tag1"];

1.3.1 Context primitives

Context primitive is a term for anything that can be used as a context. A context primitive is a self-describing JSON, or a callback that creates a self-describing JSON.

Self-describing JSON

This is useful in cases where the context is static and it’s always the same.

SPGlobalContext *staticGC = [[SPGlobalContext alloc] initWithStaticContexts:@[
    [[SPSelfDescribingJson alloc] initWithSchema:@"iglu:com.snowplowanalytics.snowplow/test_sdj/jsonschema/1-0-1" andData:@{@"key": @"value"}]
]];
[tracker addGlobalContext:staticGC tag:@"tag1"];
Context Generator Callback

A context generator callback returns an array of self describing JSONs, representing contexts. They are evaluated each time an event is sent, hence they meet the case where we would like to send a context based on event information. SPInspectableEvent is an interface that exposes internal data of the event processed: event name, schema and payload.

SPGlobalContext *blockGC = [[SPGlobalContext alloc] initWithGenerator:^NSArray<SPSelfDescribingJson *> *(id<SPInspectableEvent> event) {
    ... Computing using event informations ...
    return @[
        [[SPSelfDescribingJson alloc] initWithSchema:@"iglu:com.snowplowanalytics.snowplow/test_sdj/jsonschema/1-0-1" andData:@{@"key": @"value"}],
    ];
}];
[tracker addGlobalContext:blockGC tag:@"tag1"];

1.3.2 Conditional Context Providers

The previous examples described the generation of contexts that are associated to every event. However, there are cases where the contexts should only be applied to certain events.

Filter Callback

A filter callback is used to discriminate between events so we can attach global contexts only to certain events.

SPGlobalContext *filteredGC = [[SPGlobalContext alloc] initWithStaticContexts:@[[[SPSelfDescribingJson alloc] initWithSchema:@"iglu:com.snowplowanalytics.snowplow/test_sdj/jsonschema/1-0-1" andData:@{@"key": @"value"}]]
    filter:^BOOL(id<SPInspectableEvent> event) {
        return ["se" isEqualToString:event.name];
    }
];
[tracker addGlobalContext:filteredGC tag:@"tag1"];
Ruleset Provider

A ruleset provider is used when you want to attach a global context to certain events based on the schema URI.

A ruleset provider has a ruleset which has a list of allowed schemas and a list of denied schemas. Both lists contain Iglu URIs which can be modified based on some syntactic rules.

In this example, the ruleset provider will attach the generated contexts (as described in the previous section) to events with the schema iglu:com.acme.*/*/jsonschema/*-*-*, but not to iglu:com.acme.marketing/*/jsonschema/*-*-*.

NSString *allowed = @"iglu:com.snowplowanalytics.*/*/jsonschema/*-*-*";
NSString *denied = @"iglu:com.snowplowanalytics.mobile/*/jsonschema/*-*-*";

SPSchemaRuleset *ruleset = [SPSchemaRuleset rulesetWithAllowedList:@[allowed] andDeniedList:@[denied]];
SPGlobalContext *rulesetGC =
    [[SPGlobalContext alloc] initWithStaticContexts:@[[[SPSelfDescribingJson alloc] initWithSchema:@"iglu:com.snowplowanalytics.snowplow/test_sdj/jsonschema/1-0-1" andData:@{@"key": @"value"}]] ruleset:ruleset];
[tracker addGlobalContext:rulesetGC tag:@"tag1"];
Ruleset format

RuleSet’s rules are the strings used to match against certain schemas, such as iglu:com.acme/*/jsonschema/*-*-*.

They follow the same five-part format as an Iglu URI:

protocol:vendor/event_name/format/version

with the exception that a wildcard can be used to refer to all cases.

The parts of a rule are wildcarded with certain guidelines:

  • asterisks cannot be used for the protocol (i.e. schemas always start with iglu:).

  • version matching must be specified like so: --, where any part of the versioning can be defined, e.g. 1--, but only sequential parts can be wildcarded, e.g. 1--1 is invalid but 1-- is valid.</li><li>at least two parts: com.acme.* is valid, while com.* is not.

  • vendors cannot be defined with non-wildcarded parts between wildcarded parts: com.acme..marketing. is invalid, while com.acme.. is valid.

Context Generator

In case the logic for filter and generator callbacks are too complex, it’s possible to specify them in a class that implements SPContextGenerator protocol.

@protocol SPContextGenerator <NSObject>

/*!
 @brief Takes event information and decide if the context needs to be generated.
 @param event informations about the event to process.
 @return whether the context has to be generated.
 */
- (BOOL)filterFromEvent:(id<SPInspectableEvent>)event;

/*!
 @brief Takes event information and generates a context.
 @param event informations about the event to process.
 @return a user-generated self-describing JSON.
 */
- (nullable NSArray<SPSelfDescribingJson *> *)generatorFromEvent:(id<SPInspectableEvent>)event;

@end

In this case the logic for filtering and generation is encapsulated behind a context generator class.

@interface GlobalContextGenerator: NSObject <SPContextGenerator>
@end

@implementation GlobalContextGenerator

- (BOOL)filterFromEvent:(id<SPInspectableEvent>)event {
    return YES;
}

- (NSArray<SPSelfDescribingJson *> *)generatorFromEvent:(id<SPInspectableEvent>)event {
    return @[
        [[SPSelfDescribingJson alloc] initWithSchema:@"iglu:com.snowplowanalytics.snowplow/test_sdj/jsonschema/1-0-1" andData:@{@"key": @"value"}],
    ];
}

@end

It can be passed to the tracker as usual:

SPGlobalContext *contextGeneratorGC = [[SPGlobalContext alloc] initWithContextGenerator:[GlobalContextGenerator new]];
[tracker addGlobalContext:rulesetGC tag:@"tag1"];

2. Tracking GDPR basis for processing with the GDPR context

This release introduces the gdprContext and the enableGdprContext methods, which append a GDPR context to all events once enabled (#425). This allows users to easily record the basis for data collection and relevant documentation, and enables a straightforward audit flow for all events.

It takes the following arguments:

Name Description Required? Type
basis GDPR Basis for processing Yes Enum String
documentId ID of a GDPR basis document No String
documentVersion Version of the document No String
documentDescription Description of the document No String

The required basisForProcessing accepts only the following literals: consent, contract, legal_obligation, vital_interests, public_task, legitimate_interests - in accordance with the five legal bases for processing.

The GDPR context is enabled by calling the setGdprContextWithBasis:documentId:documentVersion:documentDescription: method of the tracker builder or by calling the enableGdprContextWithBasis:documentId:documentVersion:documentDescription: method once the tracker has been initialised.

It is called as follows:

SPTracker *tracker = [SPTracker build:^(id<SPTrackerBuilder> builder) {
    [builder setEmitter:emitter];
    ...
    [builder setGdprContextWithBasis:SPGdprProcessingBasisConsent
                          documentId:@"someId"
                     documentVersion:@"0.1.0"
                 documentDescription:@"a demo document description"];
}];

3. Swift Package Manager support

The Swift Package Manager is the official Apple dependency manager designed for Swift libraries. Since Xcode 11, SPM is compatible with the iOS build system. Thanks to the important contribution from @vauxall, we have introduced the support of Swift Package Manager for our iOS tracker (#474). At the moment it supports only the iOS platform due to some limitations that we will resolve in future releases, but it is a great option for adding our mobile tracker and a great contribution from our active community.

To install Snowplow Tracker with SPM:

  1. In Xcode, select File > Swift Packages > Add Package Dependency
  2. Add the repository of: https://github.com/snowplow/snowplow-objc-tracker
  3. Add the modules to your project’s targets in the General tab > Frameworks, Libraries & Embedded Content section

4. Deprecated methods

In this release we’ve marked a few methods as deprecated:

  • SPEvent - eventId: This shouldn’t be used as it can cause ambiguity about event duplication. This property will be set only by the tracker. If your code sets its own eventId we suggest to record your custom event ID in a custom context associated to all the events.

  • SPEvent - timestamp: In a future major version we will remove the ability to override the timestamp of the event. That will be set only by the tracker. A custom timestamp can be eventually added using a different method or parameter in a future release.

  • SPEvent - addDefaultParamsToPayload:: This shouldn’t be used as it’s an internal method used by the tracker and it can be subject to future changes.

Other methods have also been marked as deprecated because they are related to internal logic of the tracker. We strongly suggest avoiding the use of these methods marked deprecated as they will be substituted or removed in the next major version.

5. Updates and bug fixes

  • Fixed some warnings due to deprecated methods on last iOS versions (#493, #492, #479).

  • Internal events refactoring (#489).

6. Documentation

As always, information about how to use the tracker can be found in the iOS Tracker documentation.

You can find the full release notes on GitHub as Snowplow iOS Tracker v1.3.0 release.

7. Getting help

For help on integrating the tracker please have a look at the setup guide. If you have any questions or run into any problems, please visit our Discourse forum. Please raise any bugs in the iOS Tracker’s issues on GitHub.