Middleware for iOS
Middlewares are simple functions invoked by the Segment libraries, which give you a way to add information to the events you collect using the Segment SDKs. They can be used to monitor, modify, or reject events. Source Middlewares are available on analytics-ios
3.6.0 and later.
You can access the middleware API in both Objective-C and Swift.
Note: Destination Middlewares are not available for iOS.
Use
Middleware is any Objective-C class that conforms to the following protocol.
@protocol SEGMiddleware
@required
- (void)context:(SEGContext *_Nonnull)context next:(S
EGMiddlewareNext _Nonnull)next;
@end
Segment also provides a block-centric helper class to make it easier to create middlewares using anonymous functions on the fly. (See examples below)
typedef void (^SEGMiddlewareBlock)(SEGContext *_Nonnull context, SEGMiddlewareNext _Nonnull next);
@interface SEGBlockMiddleware : NSObject <SEGMiddleware>
@property (nonnull, nonatomic, readonly) SEGMiddlewareBlock block;
- (instancetype _Nonnull)initWithBlock:(SEGMiddlewareBlock _Nonnull)block;
@end
The context
object encapsulates everything about an event in the stream. You invoke the next
callback function when the current middleware is done processing the event, and can pass the processed event down to the next middleware in the chain.
The SEGContext
object is not very information rich by itself. Typically you must use eventType
and payload
to get more information about an event.
@interface SEGContext : NSObject <NSCopying>
@property (nonatomic, readonly, nonnull) SEGAnalytics *_analytics;
@property (nonatomic, readonly) SEGEventType eventType;
@property (nonatomic, readonly, nullable) NSString *userId;
@property (nonatomic, readonly, nullable) NSString *anonymousId;
@property (nonatomic, readonly, nullable) NSError *error;
@property (nonatomic, readonly, nullable) SEGPayload *payload;
@property (nonatomic, readonly) BOOL debug;
- (instancetype _Nonnull)initWithAnalytics:(SEGAnalytics *_Nonnull)analytics;
- (SEGContext *_Nonnull)modify:(void (^_Nonnull)(id<SEGMutableContext> _Nonnull ctx))modify;
@end
Look at the SEGEventType
carefully, and notice that middleware can handle track
, identify
and other Segment analytics APIs. Even calls like reset
, flush
and openURL
go through and can be processed by the middleware pipeline.
typedef NS_ENUM(NSInteger, SEGEventType) {
// Should not happen, but default state
SEGEventTypeUndefined,
// Core Tracking Methods
SEGEventTypeIdentify,
SEGEventTypeTrack,
SEGEventTypeScreen,
SEGEventTypeGroup,
SEGEventTypeAlias,
// General utility
SEGEventTypeReset,
SEGEventTypeFlush,
// Remote Notification
SEGEventTypeReceivedRemoteNotification,
SEGEventTypeFailedToRegisterForRemoteNotifications,
SEGEventTypeRegisteredForRemoteNotifications,
SEGEventTypeHandleActionWithForRemoteNotification,
// Application Lifecycle
SEGEventTypeApplicationLifecycle,
// Misc.
SEGEventTypeContinueUserActivity,
SEGEventTypeOpenURL,
};
There are almost as many SEGPayload
subclasses as there are SEGEventType
enums. Subclassed payloads may contain call specific information, For example, the SEGTrackPayload
contains event
as well as properties
.
@interface SEGTrackPayload : SEGPayload
@property (nonatomic, readonly) NSString *event;
@property (nonatomic, readonly, nullable) NSDictionary *properties;
@end
Finally, to use a middleware, you must provide it to the SEGAnalyticsConfiguration
object prior to the initialization of SEGAnalytics
.
@interface SEGAnalyticsConfiguration : NSObject
/**
* Set custom source middleware. Will be run before all integrations
*/
@property (nonatomic, strong, nullable) NSArray<id<SEGMiddleware>> *sourceMiddleware;
// ...
@end
Once initialized, the list of middleware used in SEGAnalytics
cannot be changed.
Middlewares Examples
The following examples are written in Swift to show that the middleware API works just as well in Swift as in Objective-C.
Initialize middleware
The following example shows how to initialize middleware.
let mixpanelIntegration = SEGMixpanelIntegrationFactory.instance()
let amplitudeIntegration = SEGAmplitudeIntegrationFactory.instance()
let config = AnalyticsConfiguration(writeKey: "YOUR_WRITEKEY_HERE")
config.trackApplicationLifecycleEvents = true
config.trackDeepLinks = true
config.recordScreenViews = true
config.use(mixpanelIntegration)
config.use(amplitudeIntegration)
config.sourceMiddleware = [
turnScreenIntoTrack,
enforceEventTaxonomy,
customizeAllTrackCalls,
dropSpecificEvents,
blockScreenCallsToAmplitude,
]
Analytics.setup(with: config)
id<SEGIntegrationFactory> mixpanelIntegration = [SEGMixpanelIntegrationFactory instance];
id<SEGIntegrationFactory> amplitudeIntegration = [SEGAmplitudeIntegrationFactory instance];
SEGAnalyticsConfiguration *config = [SEGAnalyticsConfiguration configurationWithWriteKey:@"YOUR_WRITEKEY_HERE"];
config.trackApplicationLifecycleEvents = YES;
config.trackDeepLinks = YES;
config.recordScreenViews = YES;
[config use:mixpanelIntegration];
[config use:amplitudeIntegration];
config.sourceMiddleware = @[
turnScreenIntoTrack,
enforceEventTaxonomy,
customizeAllTrackCalls,
dropSpecificEvents,
blockScreenCallsToAmplitude,
];
[SEGAnalytics setupWithConfiguration:config];
Change event names and add attributes
The following examples show how to changing event names, and add custom attributes.
let customizeAllTrackCalls = BlockMiddleware { (context, next) in
if context.eventType == .track {
next(context.modify { ctx in
guard let track = ctx.payload as? TrackPayload else {
return
}
let newEvent = "[New] \(track.event)"
var newProps = track.properties ?? [:]
newProps["customAttribute"] = "Hello"
ctx.payload = TrackPayload(
event: newEvent,
properties: newProps,
context: track.context,
integrations: track.integrations
)
})
} else {
next(context)
}
}
SEGBlockMiddleware *customizeAllTrackCalls = [[SEGBlockMiddleware alloc] initWithBlock:^(SEGContext * _Nonnull context, SEGMiddlewareNext _Nonnull next) {
if ([context.payload isKindOfClass:[SEGTrackPayload class]]) {
SEGTrackPayload *track = (SEGTrackPayload *)context.payload;
next([context modify:^(id<SEGMutableContext> _Nonnull ctx) {
NSString *newEvent = [NSString stringWithFormat:@"[New] %@", track.event];
NSMutableDictionary *newProps = (track.properties != nil) ? [track.properties mutableCopy] : [@{} mutableCopy];
newProps[@"customAttribute"] = @"Hello";
ctx.payload = [[SEGTrackPayload alloc] initWithEvent:newEvent
properties:newProps
context:track.context
integrations:track.integrations];
}]);
} else {
next(context);
}
}];
Change a call type
The following example turns one kind call into another. NOTE: This is only applicable to Source Middleware.
let turnScreenIntoTrack = BlockMiddleware { (context, next) in
if context.eventType == .screen {
next(context.modify { ctx in
guard let screen = ctx.payload as? ScreenPayload else {
return
}
let event = "\(screen.name) Screen Tracked"
ctx.payload = TrackPayload(
event: event,
properties: screen.properties,
context: screen.context,
integrations: screen.integrations
)
ctx.eventType = .track
})
} else {
next(context)
}
}
SEGBlockMiddleware *turnScreenIntoTrack = [[SEGBlockMiddleware alloc] initWithBlock:^(SEGContext * _Nonnull context, SEGMiddlewareNext _Nonnull next) {
if ([context.payload isKindOfClass:[SEGScreenPayload class]]) {
SEGScreenPayload *screen = (SEGScreenPayload *)context.payload;
next([context modify:^(id<SEGMutableContext> _Nonnull ctx) {
NSString *event = [NSString stringWithFormat:@"%@ Screen Tracked", screen.name];
ctx.payload = [[SEGTrackPayload alloc] initWithEvent:event
properties:screen.properties
context:screen.context
integrations:screen.integrations];
ctx.eventType = SEGEventTypeTrack;
}]);
} else {
next(context);
}
}];
Block specific events
The following example completely blocks specific events from a list.
let dropSpecificEvents = BlockMiddleware { (context, next) in
let validEvents = [
"Application Opened",
"Order Completed",
"Home Screen Tracked",
"AnalyticsIOSTestApp. Screen Tracked",
]
if let track = context.payload as? TrackPayload {
if !validEvents.contains(track.event) {
print("Dropping Rogue Event '\(track.event)'")
// not calling next results in an event being discarded
return
}
}
next(context)
}
SEGBlockMiddleware *dropSpecificEvents = [[SEGBlockMiddleware alloc] initWithBlock:^(SEGContext * _Nonnull context, SEGMiddlewareNext _Nonnull next) {
NSArray<NSString *> *validEvents = @[@"Application Opened",
@"Order Completed",
@"Home Screen Tracked",
@"AnalyticsIOSTestApp. Screen Tracked"];
if ([context.payload isKindOfClass:[SEGTrackPayload class]]) {
SEGTrackPayload *track = (SEGTrackPayload *)context.payload;
if ([validEvents containsObject:track.event]) {
NSLog(@"Dropping Rogue Event '%@'", track.event);
// not calling next results in an event being discarded
return;
}
}
next(context);
}];
Block specific call types to a specific destination
The following example blocks only screen calls from reaching the Amplitude destination.
let blockScreenCallsToAmplitude = BlockMiddleware { (context, next) in
if let screen = context.payload as? ScreenPayload {
next(context.modify { ctx in
ctx.payload = ScreenPayload(
name: screen.name,
properties: screen.properties,
context: screen.context,
integrations: ["Amplitude": false]
)
})
return
}
next(context)
}
SEGBlockMiddleware *blockScreenCallsToAmplitude = [[SEGBlockMiddleware alloc] initWithBlock:^(SEGContext * _Nonnull context, SEGMiddlewareNext _Nonnull next) {
if ([context.payload isKindOfClass:[SEGScreenPayload class]]) {
SEGScreenPayload *screen = (SEGScreenPayload *)context.payload;
next([context modify:^(id<SEGMutableContext> _Nonnull ctx) {
ctx.payload = [[SEGScreenPayload alloc] initWithName:screen.name
properties:screen.properties
context:screen.context
integrations:@{@"Amplitude": @NO}];
}]);
return;
}
next(context);
}];
Braze Middleware
If you use the Braze (Appboy) destination in either cloud or device mode you can save Braze costs by “debouncing” duplicate Identify calls from Segment by adding the open-source Middleware tool to your implementation. More information about this tool and how it works is available in the project’s README.
This page was last modified: 10 Aug 2023
Need support?
Questions? Problems? Need more info? Contact Segment Support for assistance!