The RudderStack iOS SDK lets you track the customer event data from your iOS, macOS, tvOS, and watchOS applications and send it to the specified destinations via RudderStack.
- tvOS is supported in version 1.1.0 and above
- watchOS is supported in version v1.3.1 and above
- macOS is supported in version 2.0.0 and above
Refer to the GitHub codebase to get a more hands-on understanding of the SDK.
SDK setup requirements
To set up the RudderStack iOS SDK, the following prerequisites must be met:
- Set up a RudderStack account.
- Set up an iOS source in the dashboard. For more information, refer to the Sources guide. You should then see a Write Key for this source, as shown:
- You also need a data plane URL. Follow Data plane URL section for more information on the data plane URL and where to get it.
- Finally, you will need a Mac with the latest version of Xcode.
Installing the RudderStack iOS SDK
The RudderStack iOS SDK is distributed through Cocoapods and Carthage.
The recommended and easiest way to add the SDK to your project is through Podfile
.
Follow these steps to install the SDK depending on your preferred method:
- Add the SDK to your
Podfile
, as shown:pod 'Rudder', '~> 2.0.0' - Then, run the following command:pod install
- Add the SDK to your
Cartfile
, as shown:github "rudderlabs/rudder-sdk-ios" - Then, run the following command:carthage update
Remember to include the following code in all the .m
and .h
files (Objective-C) or the .swift
files where you want to refer to or use the RudderStack SDK classes:
@import Rudder;
import Rudder
RudderStack uses SQLite to temporarily store the events before sending them to the data plane. Making calls which are not thread-safe, like SQLite.shutdown()
, might lead to unexpected crashes.
Initializing the RudderStack client
To initialize the RudderStack client, place the following code in your AppDelegate
file under the didFinishLaunchingWithOptions
method:
RSConfig *config = [[RSConfig alloc] initWithWriteKey:WRITE_KEY];[config dataPlaneURL:DATA_PLANE_URL];[config trackLifecycleEvents:YES];[config recordScreenViews:YES];
[[RSClient sharedInstance] configureWith:config];
RSClient
is accessible after the initialization via [RSClient sharedInstance]
.let config: RSConfig = RSConfig(writeKey: WRITE_KEY) .dataPlaneURL(DATA_PLANE_URL) .trackLifecycleEvents(true) .recordScreenViews(true) RSClient.sharedInstance().configure(with: config)
RSClient
is accesible after the initialization via RSClient.sharedInstance()
RudderStack automatically tracks the following application lifecycle events:
Application Installed
Application Updated
Application Opened
Application Backgrounded
trackLifecycleEvents
method of RSConfig
by passing false
. However, it is highly recommended to keep them enabled.Configuring the RudderStack client
You can configure your client based on the following parameters using RSConfig
:
Parameter | Type | Description | Default value |
---|---|---|---|
logLevel | RSLogLevel | Controls how much of the log you want to see from the SDK. | RSLogLevel.none |
dataPlaneUrl | String | Your data plane URL. | https://hosted.rudderlabs.com |
flushQueueSize | Integer | Number of events in a batch request sent to the server. | 30 |
dbCountThreshold | Integer | Number of events to be saved in the SQLite database. Once the limit is reached, older events are deleted from the database. | 10000 |
sleepTimeout | Integer | Minimum waiting time to flush the events to the server. | 10 seconds |
trackLifecycleEvents | Boolean | Determines if the SDK will capture application life cycle events automatically. | true |
recordScreenViews | Boolean | Determines if the SDK will capture will capture screen view events automatically. | false |
controlPlaneUrl | String | This parameter should be changed only if you are self-hosting the control plane. Refer to the Self-hosted control plane section below for more information. The SDK will add /sourceConfig along with this URL to fetch the required configuration. | https://api.rudderlabs.com |
Self-hosted control plane
If you are using a device mode destination like Adjust, Firebase, etc., the SDK needs to fetch the required configuration from the control plane. If you are using the Control Plane Lite utility to host your own control plane, then follow this section to specify controlPlaneUrl
in RSConfig
that points to your hosted source configuration file.
You should not pass the controlPlaneUrl
parameter during the SDK initialization if you are using RudderStack Cloud. This parameter is supported only if you are self-hosting the control plane using the Control Plane Lite utility.
Supported API calls
The iOS SDK supports all the API calls specified in the RudderStack Events Spec guide. These include identify
, track
, screen
, group
, alias
, and reset
calls.
Identify
The identify
call lets you identify a visiting user and associate them to their actions. It also lets you record the traits about them like their name, email address, etc. Once you identify the user, the SDK persists all the user information and passes it on to the subsequent track
or screen
calls. To reset the user identification, you can use the reset
method.
RudderStack captures deviceId
and uses that as anonymousId
for identifying unknown users. This helps in tracking the users across the application installation.
According to the Apple documentation, if a device has multiple apps from the same vendor, all the apps will be assigned the same deviceId
. If all the applications from the vendor are uninstalled, then a new deviceId
will be assigned to the apps on the next install.
An sample identify
call is shown below:
[[RSClient sharedInstance] identify:@"user_id" traits:@{@"email": @"alex@example.com"}];
RSClient.sharedInstance().identify("user_id", traits: ["email": "alex@example.com"])
The identify
method accepts the following parameters:
Name | Data type | Presence | Description |
---|---|---|---|
userId | NSString | Required | Uniquely identifies the visiting user. |
traits | NSDictionary | Optional | Information on the user traits. Use the dict method of RudderTraits to convert to NSDictionary easily. |
option | RSOption | Optional | Extra options for the identify event. |
Setting your own anonymous ID
By default, RudderStack uses the deviceId
as anonymousId
. To set your own anonymousId
, you can use the setAnonymousId
method as shown:
[client setAnonymousId:@"new_anonymous_id"];
RSClient.sharedInstance().setAnonymousId("new_anonymous_id")
Setting an external ID
You can pass your custom userId
along with the standard userId
in your identify
calls. RudderStack adds these values under context.externalId
.
The following code snippet highlights how you can add externalId
to your identify
requests:
RSOption *eventOption = [[RSOption alloc] init];[eventOption putExternalId:@"brazeExternalId" withId:@"some_external_id_1"]; [[RSClient sharedInstance] identify:@"user_id" traits:@{@"email": @"alex@example.com"} option:eventOption];
let messageOption = RSOption()messageOption.putExternalId("brazeExternalId", withId: "some_external_id_1")
RSClient.sharedInstance().identify("user_id", traits: ["email": "alex@example.com"], option: messageOption)
Track
The track
call lets you record the user events along with any properties associated with them.
A sample track
event is shown below:
[[RSClient sharedInstance] track:@"sample_track_event" properties:@{ @"key_1": @"value_1", @"key_2": @"value_2"}];
RSClient.sharedInstance().track("sample_track_event", properties: [ "key_1": "value_1", "key_2": "value_2"])
The track
method accepts the following parameters:
Name | Data type | Presence | Description |
---|---|---|---|
eventName | NSString | Required | Name of the tracked event. |
properties | NSDictionary | Optional | Extra data properties to be sent along with the event. |
option | RSOption | Optional | Extra event options. |
Screen
The screen
call lets you record whenever a user views their mobile screen, with any additional relevant information about the screen.
A sample screen
event is as shown:
[[RSClient sharedInstance] screen:@"ViewController" properties:@{ @"key_1": @"value_1", @"key_2": @"value_2"} ];
RSClient.sharedInstance().screen("ViewController", properties: [ "key_1": "value_1", "key_2": "value_2"])
The screen
method accepts the following parameters:
Name | Data type | Presence | Description |
---|---|---|---|
screenName | NSString | Required | Name of the screen viewed by the user. |
properties | NSDictionary | Optional | Extra property object to be passed along with the screen call. |
option | RSOption | Optional | Extra options to be passed along with the screen call. |
Group
The group
call lets you link an identified user with a group like a company, organization, or an account. It also lets you record any traits associated with that group, like the name of the company, number of employees, etc.
A sample group
call is shown below:
[[RSClient sharedInstance] group:@"sample_group_id" traits:@{ @"key_1": @"value_1", @"key_2": @"value_2"} ];
RSClient.sharedInstance().group("sample_group_id", traits: [ "key_1": "value_1", "key_2": "value_2"])
The group
method accepts the following parameters:
Name | Data type | Presence | Description |
---|---|---|---|
groupId | String | Required | The unique identifier of the group with which you want to associate your user. |
traits | NSDictionary | Optional | Any other property of the organization you want to pass along with the call. |
option | RSOption | Optional | Extra event-level options to be passed along with the group call. |
The iOS SDK does not persist the group traits across the sessions.
Alias
The alias
call associates the user with a new identification. A sample alias
call is shown below:
[[RSClient sharedInstance] alias:@"new_user_id"];
RSClient.sharedInstance().alias("new_user_id")
Alternatively, you can use the following method signature:
Name | Data type | Presence | Description |
---|---|---|---|
newId | String | Required | The new userId you want to assign to the user. |
option | RSOption | Optional | Event-level options. |
RudderStack replaces the old userId
with the newUserId
and persists that identification across the sessions.
Reset
You can use the reset
method to clear the persisted user traits from the identify
call. This is required for the user logout operation.
[[RSClient sharedInstance] reset];
RSClient.sharedInstance().reset()
Enabling/disabling user tracking via the setOptOutStatus
API (GDPR support)
RudderStack gives the users (for example, an EU user) the ability to opt out of tracking any user activity until they give their consent. You can do this by leveraging RudderStack's setOptOutStatus
API.
The setOptOutStatus
API takes YES
/NO
(Objective-C) or true
/false
(Swift) as a Boolean value to enable or disable the user tracking activities. This flag persists across device reboots.
You need to call the setoptOutStatus
API with the relevant parameter only once, as the information persists within the device even if you reboot it.
The following snippet highlights the use of the setoptOutStatus
API to disable user tracking:
[[RSClient sharedInstance] setOptOutStatus:YES];
RSClient.sharedInstance().setOptOutStatus(true)
Once the user grants their consent, you can enable user tracking once again using the setOptOutStatus
API by passing NO
or false
, as shown:
[[RSClient sharedInstance] setOptOutStatus:NO];
RSClient.sharedInstance().setOptOutStatus(false)
Supporting push notifications for device mode destinations
With the iOS SDK, you need not call the individual destination's API in your app to implement the push notifications support; calling the SDK's push notification API is sufficient.
To enable push notifications for your device mode destinations, the SDK provides the following functions:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler;
- func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data)
- func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error)
- func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
- func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void)
To implement these functions, go to your AppDelegate
file and add the following lines:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [[RSClient sharedInstance] application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [[RSClient sharedInstance] application:application didFailToRegisterForRemoteNotificationsWithError:error];}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler { [[RSClient sharedInstance] userNotificationCenter:center didReceive:response withCompletionHandler:completionHandler];}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { [[RSClient sharedInstance] application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];}
- func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { RSClient.sharedInstance().application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)}
- func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { RSClient.sharedInstance().application(application, didFailToRegisterForRemoteNotificationsWithError: error)}
- func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { RSClient.sharedInstance().userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler)}
- func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { RSClient.sharedInstance().application(application, didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler)}
Setting the device token for push notifications
To pass push notifications to the destinations that support it, you can pass your device token using the setDeviceToken
method, as shown:
[[RSClient sharedInstance] setDeviceToken:@"example_device_token"];
RSClient.sharedInstance().setDeviceToken("example_device_token")
RudderStack sets the device token under context.device.token
.
Setting the advertisement ID
RudderStack separates the IDFA collection from the core library so that you have better control over it.
You can pass the IDFA to the setAdvertisementId
method to set it under context.device.advertisingId
, as shown:
[[RSClient sharedInstance] setAdvertisingId:[self getIDFA]];
- (NSString*)getIDFA { return [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];}
RSClient.sharedInstance().setAdvertisingId(getIDFA())
func getIDFA() -> String { return ASIdentifierManager.shared().advertisingIdentifier.uuidString}
ATTrackingManager
authorization consent
You can pass ATTrackingManager.trackingAuthorizationStatus
to RudderStack and it is passed along to the relevant destinations as configured.
For example, AppsFlyer accepts this parameter for the attribution to work in their server-to-server events flow.
[[RSClient sharedInstance] setAppTrackingConsent:RSAppTrackingConsentAuthorize];
RSClient.sharedInstance().setAppTrackingConsent(.authorize)
You can pass the following options to the setAppTrackingConsent
method to set the relevant authorization consent:
RSATTNotDetermined
RSATTRestricted
RSATTDenied
RSATTAuthorize
Filtering device mode events
When sending events to a destination via the device mode, you can explicitly specify which events should be discarded or allowed to flow through - by allowlisting or denylisting them.
Enabling/disabling events for specific destinations
The RudderStack iOS SDK lets you enable or disable event flow to a specific destination or all the destinations to which the source is connected. You can specify these destinations by creating a RSOption
object as shown:
RSOption *option = [[RSOption alloc]init];//default value for `All` is true[option putIntegration:@"All" isEnabled:YES];// specifying destination by its display name[option putIntegration:@"Amplitude" isEnabled:YES];[option putIntegration:@"<DESTINATION_DISPLAY_NAME>" isEnabled:<BOOLEAN>];
let option:RSOption = RSOption();//default value for `All` is trueoption.putIntegration("All", isEnabled:true)// specifying destination by its display nameoption.putIntegration("Amplitude", isEnabled:true)option.putIntegration(<DESTINATION_DISPLAY_NAME>, isEnabled:<BOOLEAN>)
The keyword All
in the above snippet represents all the destinations the source is connected to. The SDK sets its value to true
by default.
Make sure the DESTINATION_DISPLAY_NAME
you specify above should exactly match the destination name as shown in the RudderStack dashboard.
You can pass the destinations specified to the SDK in the following two ways:
Method 1: Passing destinations while initializing the SDK
This approach is helpful when you want to enable/disable sending the events to the destinations across all the event calls made using the SDK.
RSOption *defaultOption = [[RSOption alloc] init];[defaultOption putIntegration:@"Amplitude" isEnabled:YES];
[[RSClient sharedInstance] setOption:defaultOption];
let defaultOption = RSOption()defaultOption.putIntegration("Amplitude", isEnabled: true)
RSClient.sharedInstance().setOption(defaultOption)
Method 2. Passing destinations while making event calls
This approach is helpful when you want to enable/disable sending only specific events to the destinations.
RSOption *eventOption = [[RSOption alloc] init];[eventOption putIntegration:@"Amplitude" isEnabled:YES];
[[RSClient sharedInstance] track:@"sample_track" properties:@{@"key_1": @"value_1", @"key_2": @"value_2"} option:eventOption];
let eventOption = RSOption()eventOption.putIntegration("MoEngage", isEnabled: true)
RSClient.sharedInstance().track("sample_track", option: eventOption)
If you use the RSOption
object to specify the destinations both while initializing the SDK as well as making an event call, then RudderStack will consider only the destinations specified at the event level.
Debugging
If you run into any issues regarding the RudderStack iOS SDK, you can enable VERBOSE
or DEBUG
logging to determine the issue.
To enable the logging, change your RSClient
initialization as shown:
RSConfig *config = [[RSConfig alloc] initWithWriteKey:WRITE_KEY];[config dataPlaneURL:DATA_PLANE_URL];[config loglevel:RSLogLevelDebug];
[[RSClient sharedInstance] configureWith:config];
let config: RSConfig = RSConfig(writeKey: WRITE_KEY) .dataPlaneURL(DATA_PLANE_URL) .loglevel(.debug) RSClient.sharedInstance().configure(with: config)
Adding Chromecast support
Google Chromecast is a device that plugs into your TV or monitor with an HDMI port, and can be used to stream content from your phone or computer.
RudderStack supports integrating the iOS SDK with your Cast app. Follow these instructions to build your iOS sender app. Then, add the iOS SDK to it.
Follow the Google Cast developer guide for more details.
Developing a device mode destination
This section details the steps required to develop a device mode destination in case RudderStack doesn't support it already.
- Create a
RSCustomDestination.swift
file by extendingRSDestinationPlugin
, as shown:
class RSCustomDestination: RSDestinationPlugin { var key: String = "Custom" var controller = RSController() var type: PluginType = .destination var RSClient.sharedInstance(): RSClient? func update(serverConfig: RSServerConfig, type: UpdateType) { guard type == .initial else { return } // Some code } func track(message: TrackMessage) -> TrackMessage? { // Some code return message } func identify(message: IdentifyMessage) -> IdentifyMessage? { // Some code return message } func screen(message: ScreenMessage) -> ScreenMessage? { // Some code return message } func group(message: GroupMessage) -> GroupMessage? { // Some code return message } func alias(message: AliasMessage) -> AliasMessage? { // Some code return message } func flush() { // Some code } func reset() { // Some code }}
- Then, create a
CustomDestination
class file by extendingRudderDestination
and initializeRSCustomDestination
insideinit()
, as shown:
@objcclass CustomDestination: RudderDestination { override init() { super.init() plugin = RSCustomDestination() }}
For Objective-C projects, a dialog box will appear while creating the Swift file - asking you to create a bridging header if it does not exist already. In this case, choose Create Bridging Header. This creates a
<PROJECT_MODULE_NAME>-Bridging-Header.h
file.After creating the Bridging Header, search Objective-C Generated Interface Header Name in Build Settings and keep the header name handy. This should be something like
<PROJECT_MODULE_NAME>-Swift.h
.Finally, add the
CustomDestination
with the RudderStack iOS SDK after its initialization, as shown:
RSClient.sharedInstance().addDestination(CustomDestination())
#import "<PROJECT_MODULE_NAME>-Swift.h"
[[RSClient sharedInstance] addDestination:[[CustomDestination alloc] init]];
FAQ
I'm facing issues building with Carthage on XCode 12. What should I do?
If you're facing an issue with Carthage and XCode 12, you can follow this workaround suggested by the Carthage team.
Does the iOS SDK support the tvOS, macOS, and watchOS platforms?
Yes, the iOS SDK supports tvOS and watchOS platforms. Refer to the table below for the version details:
How do I migrate from an older SDK version (v1.x) to the current version?
To migrate from the older SDK versions, update the usage of the following classes:
Previous Name | Updated Name |
---|---|
RudderClient | RSClient |
RudderConfig | RSConfig |
RudderLogLevelDebug | RSLogLevelDebug |
How can I get the user traits
after making the identify
call?
You can get the user traits after making an identify
call in the following way:
let traits = RSClient.sharedInstance().traits
NSDictionary *traits = [RSClient sharedInstance].traits;// orNSDictionary *traits = [[RSClient sharedInstance] traits];
How does the SDK handle different client/server errors?
In case of client-side errors, for example, if the source write key passed to the SDK is incorrect, RudderStack gives a 400 Bad Request response and aborts the operation immediately. For other types of network errors, for example, invalid data plane URL, the SDK tries to flush the events to RudderStack in an incremental manner (every 1 second, 2 seconds, 3 seconds, and so on).
Why is there a difference between timestamp
and received_at
for iOS events vs. Android events sent at the same time?
This scenario is most likely caused by the default behavior of iOS apps staying open in the background for a short period of time after a user closes them.
When a user closes an iOS or Android app, the events will still continue to be sent from the queue until the app closes in the background. Any events still in the queue will be retained until the user reopens the app. Due to this lag, there are some scenarios where there can be significant differences between timestamp
(when the event was created) and received_at
(when RudderStack actually receives the event).
For Android apps, events can be sent from the background after apps close for a longer period of time than iOS apps, therefore, more of the events coming from the Android SDK have closer timestamp
and received_at
times.
Does RudderStack integrate with SKAdNetwork?
RudderStack does not integrate with SKAdNetwork. However, SKAdNetwork can be directly integrated into an iOS application alongside RudderStack.
Can I disable event tracking until the user gives their consent?
Yes, you can. Refer to the Enabling/disabling user tracking via the setOptOutStatus
API section for more information.
Contact us
For queries on any of the sections covered in this guide, you can contact us, or start a conversation in our Slack community. You can also open a new issue on the SDK's GitHub page.