A lightweight analytics reporting package for iOS and macOS projects.
You may want to gain the insights that analytics reporting can provide to your iOS or Mac app, but don’t want to take on the unknowns, or the potentially intrusive oversharing that some third party analytics packages perform. SimpleAnalytics is an alternative that puts all the control of what goes out, and who it goes to, into your hands.
SimpleAnalytics allows you to capture user actions in your apps and submit them to a server you control or have access to. The companion project, SimpleAnalytics Reader (see the Viewing collected data section below), lets you access the data you collect in a Macintosh desktop application.
See the SimpleAnalyticsDemo project for a rudimentary example of using it in an Xcode project.
In July 2023, Apple announced that it would require developers to report use of “required reason” APIs, including UserDefaults, beginning in the fall of 2023. Apple Developer page
Since the original version of Simple Analytics made calls to UserDefaults to persist an identifying string that was unique to the device across app launches, you would need to inlude that usage in your app releases going forward.
In version 3.0 and later of Simple Analytics, the API for initializing an analytics reporting session has changed. Its setEndpoint… method now requires passing in a device identifier string. All use of UserDefaults have been removed from the framework.
If you are upgrading from an earlier version of Simple Analytics, you will need to update your call to the setEndpoint method in your app’s code. In addition you will need to devise your own method for persisting an identifier if that is something you want to include in your analytics reports. One such possibility that should meet Apple’s new rules is used in the accompanying demo projects’ AnalyticsManager files.
SimpleAnalytics is distributed as a Swift package, which you can load into Xcode projects using the available tools built into Xcode 11.0 and higher.
In the Swift Packages section of the Project configuration panel, include a dependency for SimpleAnalytics with the URL:
https://github.com/dennisbirch/simple-analytics
Be sure to add SimpleAnalytics to your target’s Frameworks list if it isn’t added automatically.
Once you’ve added SimpleAnalytics to your Xcode project, you can begin adding code to begin capturing app usage data.
The first step is to configure the app to point to a web service that you’ve set up to handle SimpleAnalytics requests.
You can call into the SimpleAnalytics package from anywhere in your application’s code by importing the SimpleAnalytics module, and then using one of the two static methods available to record observations. As described in more detail below, there are methods for 1) recording an action with optional additional details, and 2) incrementing a counter when an action occurs.
In code that could look like:
AppAnalytics.addItem("Logged out")
AppAnalytics.addItem("Logged in", params: ["method" : loginMethod])
AppAnalytics.addItem("Changed 'Timeout' settings", params: ["minutes" : String(newTimeout),
"apply to" : String(describing: applyToOptions)])
AppAnalytics.countItem("Foregrounded app")
AppAnalytics.countItem("Set new password")
See any of the example applications for other usage examples, and an approach to centralizing calls to the SimpleAnalytics module in one controller type.
There are four different Xcode projects demonstrating use of the SimpleAnalytics package in different environments, each in a different folder:
To run any of them, simply open the xcodeproj file within the folder.
A companion open-source project, SimpleAnalytics Reader is available to allow you to view the data your apps are collecting about their usage. You can find it at https://www.github.com/dennisbirch/simple-analytics-reader. It includes source for a macOS app, and a backend web app you can run as is, or use for inspiration to write your own back-end. The interface offers different ways of querying for and viewing data to fit your needs.
The SimpleAnalytics package requires one configuration step for full implementation, and offers a couple of other configuration options.
In order to submit your app’s analytics data from users’ devices to a web service where you can access it, you’ll need to set the endpoint for the web service. The web service can be any web application that can handle a JSON payload.
New in version 2.0 and higher: On iOS, you also need to provide a reference to the shared application instance. SimpleAnalytics uses this to request and dispose of a background task when submitting data to your web service.
call the AppAnalytics.setEndpoint(_:, deviceID:, submissionCompletionCallback:)
method.
Parameters:
urlString: String for your web service URL.
deviceID: String identifying the device if desired to track in your analytics reports. See the discussion of this parameter in the “Important v3.0 note” section above.
submissionCompletionCallback: An optional completion with no argument and no return value to signal to your macOS app that submission is complete. This can be useful to implement a strategy for terminating the app after analytics submission has completed.
#####To set the endpoint and shared app on iOS:
call the AppAnalytics.setEndpoint(_:, deviceID:, sharedApp:)
method.
Parameters:
urlString: String for your web service URL.
deviceID: String identifying the device if desired to track in your analytics reports. See the discussion of this parameter in the “Important v3.0 note” section above.
sharedApp: Pass the UIApplication.shared property to this argument.
This call should be made as early as possible in your app’s lifecycle.
To help differentiate data entries, SimpleAnalytics includes a field for the platform for every entry. The framework automatically assigns the values iOS and macOS for those platforms along with the device type for iOS. But if your app is running in a hybrid environment (e.g. iOS app running on Mac), you can override that assignment with the AppAnalytics.setPlatform(_:)
method.
Parameters:
platformName: String with a platform name.
SimpleAnalytics automatically attempts to submit its contents when the total count of its data items reaches the maximum count value. The default maximum value is 100, but you can change that to fit the needs of your application. To change the maximum count value, call the AppAnalytics.setMaxItemCount(_:)
method.
Parameters:
count: Int defining the base maximum number of items to accumulate before attempting to submit them to your server.
When a submit attempt fails, SimpleAnalytics restores the items it was attempting to submit for a subsequent resubmit attempt. It also increments the maximum count value to add a delay before subsequent new events trigger another submission attempt. The default value for this failure increment is 20. You can change that by calling the AppAnalytics.setSubmitFailureIncrement(_:)
method.
Parameters:
increment: Int defining the amount to be added to the maximum item count before again attempting to submit entries.
SimpleAnalytics’ automatically attempts to submit its data when going into the background. You can override that behavior by calling the AppAnalytics.overrideSubmitAtDismiss(: )
method.
Parameters:
shouldSubmit: Bool. Pass in false to turn off automatic submission, or true to turn the behavior back on.
SimpleAnalytics offers two ways of recording analytics data.
Analytics item: This option lets you define a name for the item and optionally include a [String:String] dictionary of additional parameters. It is your responsibility to ensure that you do not include private user information.
Counter: This option allows you to name an action or behavior you want to record, and increment a counter each time it’s repeated.
To add an analytics item, call AppAnalytics.addItem(_: parameters:)
.
Parameters:
description: String describing the action or user interaction
params: An optional [String:String] dictionary of additional details to record (e.g. certain app state observations) for more refined analysis
To add or increment a counter, call AppAnalytics.countItem(_:)
. Note that adding a new counter adds an item to the total count. Incrementing an existing counter does not add to the item count. Because of this and the fact that counters do not include additional details and do not receive a timestamp, they are more lightweight than analytics items.
Parameters:
description: String describing the item to be counted. An item is added and set to a value of 1, or incremented by 1.
SimpleAnalytics keeps all the analytics entries you add in two arrays in the AppAnalytics
shared instance until it attempts to submit the data to your server. By default, SimpleAnalytics attempts submissions after a total of 100 items have accumulated, and when the application hosting it is sent to the background.
If a submission attempt fails, the items that were removed from the AppAnalytics arrays are restored for the next attempt.
As discussed above in the Configuration section, you can configure some values to influence when AppAnalytics submits data.
You can trigger a submission attempt at any time by calling the AppAnalytics.submitNow()
method.
It may be helpful in determining whether to force a submission attempt to know the total count of the AppAnalytics data arrays. To do so, access the AppAnalytics.itemCount
public property.
AppAnalytics submits its data in a JSON payload with the following format:
Label | Contents |
---|---|
items | An array of analytics item entries, each of which includes: |
description: The description of the item as defined by the call to AppAnalytics.addItem(_:, parameters) | |
parameters: An optional String:String dictionary of additional details | |
device_id: A string with a unique identifier for your app on each device running it | |
app_name: A string with the name of the application where the analytics item was generated | |
app_version: A string with the application’s version number, as defined in its info.plist file | |
platform: A string with the name of the platform in which the app was running (iOS or macOS), and the device type (iPhone or iPad) for iOS | |
system_version: A string with the operating system version the user is running | |
timestamp: A string with the date and time that the item was generated, in ISO8661 format for the user’s timezone | |
counters | An array of ‘counters’, each of which includes: |
name: The name of the action being counted, as defined by the call to AppAnalytics.countItem(_😃 | |
count: The number of times the event was counted during the current analytics collection session | |
device_id: A string with a unique identifier for your app on each device running it | |
app_name: A string with the name of the application where the analytics item was generated | |
app_version: A string with the application’s version number, as defined in its info.plist file | |
platform: A string with the name of the platform in which the app was running (iOS or macOS), and the device type (iPhone or iPad) for iOS | |
system_version: A string with the operating system version the user is running | |
timestamp: A string with the date and time that the first count was recorded, in ISO8661 format for the user’s timezone |
Your web app should respond to handling the payload with a JSON payload in the following format:
Label | Contents |
---|---|
message | A string with any message. In the default implementation, the string is logged in debug builds. |
NOTE: Failure to send a properly formatted response will cause SimpleAnalytics to resend the same items again.
See the Analytics.php file included in the package for an example of how you might handle the incoming JSON payload and send a response on a web server.
SimpleAnalytics provides methods for persisting collected data to disk and restoring it from that persistence store. It does not call either of these methods by default to avoid that being responsible for duplicate entries. If you would like to manage when data is saved and restored, these methods are available to you.
To persist analytics data, call the AppAnalytics.persistContents()
method.
To restore saved contents (e.g. at app launch), call the AppAnalytics.restorePersistenceContents()
method.
Contributions are welcome. To suggest an improvement, please submit a pull request.