- Overview
- Adding Analytics
- Tracking Feature Use
- Sending Custom Data
- Getting System Information
- Reporting Exceptions
- Advanced Configuration
- User Opt-In
- Information About Your Application
- Binary Information
- Storing Data Offline
- Controlling Data Transmission
- Field Size Limits
- API Language Interoperability
- Invalid Characters
- The Flow Controller
- Client-Side Logging
Overview
Supported Platforms
The PreEmptive Analytics iOS API supports iPod, iPad, and iPhone development for iOS 7.0 through 9.x.
The PAClient
class is thread-safe and instance methods can be used freely from multiple threads. All instance methods should return very quickly except for applicationStop
. Messages are added to the queue on the current
thread and then transmitted/saved on a background thread.
The change log can be found here.
Setup
To use the client in your iOS app, you will need to add the PA_API.framework
to your project. Instructions for Xcode® 7.x:
- Open your Project Navigator.
- Choose the target.
- Go to the
Build Phases
tab. - Expand the
Link Binary With Libraries
section. - Click the
+
. - Select
Add Other...
. - Browse to
PA_API.framework
and chooseOpen
.
If you code in Objective-C, you can then #import <PA_API/PAClient.h>
(and other headers you might need).
If you are coding in Swift, you will need to setup a bridging header.
- Add
SwiftPAHeader.h
to your project. (Make sure "Copy If Needed" is selected) - Open your Project Navigator.
- Choose the target.
- Go to
Build Settings
tab. - Expand the
Swift Compiler - Code Generation
section. - Set the
Objective C Bridging Header
to include the project-relative path forSwiftPAHeader.h
.
The API documentation can be found here.
To browse the API documentation from inside Xcode®, shutdown Xcode® and run installDocset.sh
. To remove the API documentation from Xcode®, shutdown Xcode® and run uninstallDocset.sh
.
Message Queuing and Transmission
Messages are not immediately sent to the configured endpoint. The API queues messages and sends them either when a certain amount of time has elapsed, or when a number of messages have accumulated. On platforms where transmission may have a performance impact, such as on mobile devices, the transmission of messages can be directly controlled by using Send Disabled in your program.
Message transmission is not guaranteed and some messages may be lost. In cases of forced application shutdown or storage limitations the API may be unable to transmit queued messages or store them on disk for later transmission. Messages may also be discarded if they overflow the API's queue before they can be transmitted.
Offline Storage
Your application is not required to always have network connectivity. By default, the API will store messages locally when the configured endpoint cannot be reached. These messages will automatically be sent and removed from offline storage once the endpoint can be reached.
API Usage Pattern
The general usage pattern of the API is:
- A
PAConfiguration
object is created that defines the company sending the message, the application being reported on, and additional options. - An instance of
PAClient
is created using the previously created configuration object. - The client begins its session with an
ApplicationStart
. - The client requests the transmission of multiple messages which include:
FeatureTick
to mark the occurrence of a tracked event.FeatureStart
andFeatureStop
paired messages to record the duration of a tracked event.SystemProfile
to report information about the platform.PerformanceProbe
to report CPU and memory usage.
- The client ends its session with an
ApplicationStop
.
Adding Analytics
Start up and Shut down
The API is idle until applicationStart
is called. At this point, the API queues the start message and is ready to process other messages. It remains ready until applicationStop
is called. If a message function such as featureTick
is
used before the API is ready, it will be treated as a no-operation.
You will need to choose where you want to put the Application Start and Application Stop. While it is typical to have one of each, you can use multiple Starts and/or multiple Stops depending upon the entry points and exit points to your application. Extra calls to Start and Stop don't nest: it's the first of each one that defines starting and stopping points of your application.
Company and Application ID
To start sending analytic messages you will need two IDs:
- A Company ID
- An Application ID
Both of these values are GUIDs. The Company ID represents your company or division. Your Application ID represents your application across versions and platforms.
Application Start
The only thing you need to configure before you call Application Start is a Company ID and an Application ID. You will create the PAClient
with a PAConfiguration
object, allowing for more advanced configuration options.
Objective-C Example
PAConfiguration* config = [[PAConfiguration alloc]
initWithCompanyID:@"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3"
appID:@"21EC2020-AAA1-1069-A2DD-08002B30309D"];
PAClient* client = [[PAClient alloc] initWithConfiguration:config];
[client applicationStart];
Swift Example
let config = PAConfiguration(companyID:"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3",
appID:"21EC2020-AAA1-1069-A2DD-08002B30309D")
let client = PAClient(configuration: config)
client.applicationStart()
Of course, there are many other configuration options you can set. Typical options include:
- Additional Application Information
- Name
- Version
- Type
- Instance ID (serial number)
- Endpoint Information
- Location
- Use SSL
- User Privacy Options
- User Opt-In
- Omit Personal Identifiable Data
After the PAClient
has been started, by calling applicationStart
, changes to PAConfiguration
values will not affect the API's behavior.
Note: If you set Use SSL
to NO/false, you will need to set Allow Arbitary Loads
(Under App Transport Security Settings
) to Yes
for iOS 9 support.
Application Stop
Just before your application terminates you should make a call to Application Stop. This generates the messages that are used to calculate the duration of the user's interaction with your application and handles any messages queued in memory.
Objective-C Example
[client applicationStop];
Swift Example
client.applicationStop()
There are optional arguments you can pass to the Application Stop as well as to most of the other API functions. These are covered in the Sending Custom Data and Information About Your Application sections.
Application Stop is synchronous and will send any remaining messages (provided transmission is allowed).
Tracking Feature Use
The most common usage of analytics is to track which features are popular among users and how they interact with them. You can indicate that a feature was used by calling the featureTick
method on PAClient
. You can track
the duration of a feature's use by calling featureStart
and featureStop
.
Feature Ticks
The featureTick
method generates a message that the configured endpoint tallies up. All you need to provide is a name that defines the feature:
Objective-C Example
-(void) constructTriangle:(NSUInteger)rows {
[client featureTick:@"ConstructTriangle"];
// ...
}
Swift Example
func constructTriangle (rows:UInt) {
client.featureTick("ConstructTriangle")
// ...
}
You can place calls to Feature Tick anywhere in your code, it need not have a one-to-one mapping of methods to features.
Feature Starts and Stops
A pair of Feature Start and Stops is used to measure not only the occurrence of a feature usage but also the duration of its use. One possible way to use it is to bracket the entire body of the function with a Feature Start and Feature Stop. This is useful to time how long a function takes to execute:
Objective-C Example
-(void) constructTriangle:(NSInteger)rows {
[client featureStart:@"ConstructTriangle"];
// ...
[client featureStop:@"ConstructTriangle"];
}
Swift Example
func constructTriangle (rows:Int) {
client.featureStart("ConstructTriangle");
// ...
client.featureStop("ConstructTriangle");
}
It is important that you use the exact same name in the Start and Stop calls otherwise it will look like two separate features.
Feature Start and Stop can also be used to determine how much time a user spends using a certain feature.
Objective-C Example
-(void) enterTextEditor {
[client featureStart:@"TextEditor"];
// ...
}
-(void) leaveTextEditor {
// ...
[client featureStop:@"TextEditor"];
}
Swift Example
func enterTextEditor() {
client.featureStart("TextEditor");
// ...
}
func leaveTextEditor() {
// ...
client.featureStop("TextEditor");
}
A function can contain any number of Feature Starts and Stops, and there does not have to be a one-to-one mapping of functions to features.
Feature Start/Stop Partitioning
Feature Starts and Stops are "partitioned". To demonstrate this, look at this example:
- Thread A calls
client.featureStart("foo")
- Thread B calls
client.featureStart("foo")
- Thread A calls
client.featureStop("foo")
- Thread B calls
client.featureStop("foo")
A naive implementation would result in Thread A's stop ending the feature which Thread B started, which is most likely unintended. The Feature Partitioner will "partition" sets of starts and stops based on Thread ID. So, in this case, Thread A's stop would properly match Thread A's start, not Thread B's.
However, consider this scenario:
- Thread A calls
client.featureStart("foo")
- Thread B calls
client.featureStop("foo")
In this scenario, the start and stop would match to each other. If a stop
can't find a start
within its own partition, then it will look at all of the tracked start
s in the current client instance.
Sending Custom Data
You can send custom data to the configured endpoint with any type of message. To send over the data you construct an object to hold key-value pairs. You use the provided PAExtendedKeys
class. You can send custom data with any type of
message.
One common use case is to report the arguments a method was called with and what the method will return:
Objective-C Example
-(BOOL) constructTriangle:(NSInteger)rows {
BOOL result = NO;
PAExtendedKeys* keys = [[PAExtendedKeys alloc] init];
[keys addKey:@"rows" integer:rows];
[client featureStart:@"ConstructTriangle" keys:keys];
// ...
[keys addKey:@"result" boolean:result];
[client featureStop:@"ConstructTriangle" keys:keys];
return result;
}
Swift Example
func constructTriangle (rows:Int) -> Bool {
let result = false
let keys = PAExtendedKeys()
keys.addKey("rows", integer:rows)
client.featureStart("ConstructTriangle", keys:keys)
// ...
keys.addKey("result", boolean:result);
client.featureStop("ConstructTriangle", keys:keys)
return result
}
Values for keys can be either numeric or strings. You can use the same PAExtendedKeys
structure as many times as you need. If you set a key with the same name a second time its original value is replaced.
ExtendedKeys have some limits. The length of the name for the key is limited to 2000 characters and the value part for strings is limited to 4000 characters. Numeric values can have up to 18 digits of precision with 5 digits to the right of the decimal point.
Getting System Information
The API can generate two types of system information: a system profile and performance information.
System Profile
The System Profile generates information about the operating system, its environment, and the hardware. You can generate the profile by calling the systemProfile
method on PAClient
:
Objective-C Example
//...
[client systemProfile];
//...
Swift Example
//...
client.systemProfile()
//...
It's recommended that you only generate one system profile per application run. This is typically done immediately after the Application Start. The System Profile sends back information about many different aspects of the machine running your application:
- Logical Disk - size, free space
- Memory - capacity
- Network Adapter - IP address, MAC address
- Display - Vertical and horizontal resolution
The Omit Personal Info (PII) setting affects the data reported in the profile:
- The default is to report on information that could identify the user and their machine. If Omit Personal Info is set to NO the API will skip or mask values such as IP addresses, MAC identifiers, and names that could be used to identify the user and their machine.
Performance Probe
The Performance Probe reports information about the CPU usage percentage, the amount of memory available to the application, and the amount of memory used by the application. In contrast to the System Profile, the Performance Probe is designed to be used in multiple places and called many times in your application. The Full Data setting affects the performance probe.
- The default is to report CPU and memory usage information. If Full Data is set to NO then CPU usage information will not be sent.
The Performance Probe includes a name so you can track performance information in multiple places in your application:
Objective-C Example
-(void) constructTriangle:(NSInteger) rows {
[client performanceProbe:@"ConstructTriangle.Starting"];
// ...
[client performanceProbe:@"ConstructTriangle.Ending"];
}
Swift Example
func constructTriangle(rows:Int)
{
client.performanceProbe("ConstructTriangle.Starting")
// ...
client.performanceProbe("ConstructTriangle.Ending")
}
Reporting Exceptions
The API provides a simple way to report exceptional conditions in your application. The exception reports can be used to track exceptions reported by your application or from third party software. The report can also have user added information added to it to aid support staff. And of course you can always add Extended Key information to track application state.
Exception Event Types
Exception events come in three types:
- Caught
- This represents an exception that is caught by your application. Your code can either be dealing with the exception or is going to pass it up the execution chain. This is the mostly commonly used event type.
- Thrown
- This represents an exception that your application is creating. You would use this type for an exception event that is atypical for your application.
- Uncaught
- This type is usually used in catch-all handlers and indicates that your application encountered an exception it was not prepared to catch.
In the following example will show you how to set the event type.
Reporting Details
To pass all the information about the exception you use an PAExceptionInfo
instance. You must fill it in with information such as:
- Exception type
- Exception name
- Exception message
- The
NSError
orNSException
itself (optional).
Note: Some of this information can be gathered from an Objective-C iOS applications by simply passing the relevant NSError
or NSException
object into PAExceptionInfo
.
Objective-C NSException Example
//...
@catch (NSException* exception)
{
PAExceptionInfo* eInfo = [[PAExceptionInfo alloc] initWithNSException:exception
type:CAUGHT_EXCEPTION];
[client reportException:eInfo];
//...
}
//...
Objective-C NSError Example
//...
NSError* error=nil;
if (![[NSFileManager defaultManager] removeItemAtPath:@"SomePath"
error:&error]) {
if (error) {
PAExceptionInfo* eInfo = [[PAExceptionInfo alloc] initWithNSError:error
type:CAUGHT_EXCEPTION];
[client reportException:eInfo];
}
}
//...
Although Swift does not have exception handling, you can send messages.
Swift Exception Example
//...
if anIssueOccured {
let issue = PAExceptionInfo(name: "FileSystemIssue",
message: "Could not store to directory.", type: CAUGHT_EXCEPTION)
client.reportException(issue)
}
//...
Swift NSError Example
//...
var error: NSError?
if !NSFileManager.defaultManager().removeItemAtPath("SomePath",
error:&error) {
if (error != nil) {
let eInfo = PAExceptionInfo(NSError:error,
type:CAUGHT_EXCEPTION)
client.reportException(eInfo)
}
}
//...
An NSException
will have a stack trace reported. To indicate the origin of an NSError
(or any other type), you can create a PABinaryInfo instance that will accompany the PAExceptionInfo
.
Objective-C Example
-(BOOL) checkRegistration:(NSString*) registrationCode {
BOOL validCode=NO;
//...
if (!validCode) {
PAExceptionInfo* eInfo = [[PAExceptionInfo alloc] initWithName:@"InvalidRegistrationCode"
message:[NSString stringWithFormat:@"Invalid code: %@", registrationCode]
type:THROWN_EXCEPTION];
PABinaryInfo* info = [[PABinaryInfo alloc] init];
[info setName:@"MyApplication"];
[info setClassName:@"RegistrationDelegate"];
[info setMethodName:@"checkRegistration:"];
[client reportException:eInfo info:info];
}
return validCode;
}
Swift Example
func checkRegistration(registrationCode:String) -> Bool {
let validCode=false
//...
if !validCode {
let eInfo = PAExceptionInfo(name:"InvalidRegistrationCode",
message:"Invalid code: \(registrationCode)",
type:THROWN_EXCEPTION)
let info = PABinaryInfo()
info.name="MyApplication"
info.className="RegistrationDelegate"
info.methodName="checkRegistration"
client.reportException(eInfo, info:info)
}
return validCode
}
Adding User Details
Many GUI programs can display a message box indicating the error that has occurred to the user. This is an opportunity to have the user enter some additional information that developers can use to diagnose the problem. PAExceptionInfo
provides
a way to attach up to 500 characters of text to the exception report that the user can use to describe the circumstances of the event. It also has a way to pass in contact information, such as a user name or email address.
Objective-C Example
//...
@catch (NSException* exception)
{
NSString* contact = nil;
NSString* comment = nil;
// Prompt user for information
//...
PAExceptionInfo* eInfo = [[PAExceptionInfo alloc] initWithNSException:exception
type:CAUGHT_EXCEPTION];
[eInfo setContactInfo:contact];
[eInfo setComment:comment];
[client reportException:eInfo];
//...
}
//...
Swift Example
//...
var contact = ""
var comment = ""
// Prompt user for information
//...
let eInfo = PAExceptionInfo(name: "UnexpectedIssue",
message: "Error code: \(errorCode)", type: CAUGHT_EXCEPTION)
eInfo.contactInfo=contact
eInfo.comment=comment
client.reportException(eInfo)
//...
Advanced Configuration
User Opt-In
Your application should allow the user to decide if analytics data is gathered or not. The API provides two ways to control the user opting-in to analytics gathering.
The first way to tell the API about the user's opt-in is via the PAConfiguration
. You use this way when your application stores the user's previous selection somewhere:
Objective-C Example
//...
PAConfiguration* config = [[PAConfiguration alloc]
initWithCompanyID:@"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3"
appID:@"21EC2020-AAA1-1069-A2DD-08002B30309D"];
[config setOptIn:[myPreferencesClass optIn];
//Where myPreferences is a class which persists the user's setting.
PAClient* client = [[PAClient alloc] initWithConfiguration:config];
//...
Swift Example
//...
let config = PAConfiguration(companyID:"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3",
appID:"21EC2020-AAA1-1069-A2DD-08002B30309D")
config.setOptIn(myPreferencesClass.optIn())
//Where myPreferences is a class which persists the user's setting.
let client = PAClient(configuration: config)
//...
If the optIn value is set to NO, then calls to ApplicationStart will not start the API.
The second way the opt-in option can be changed is by talking directly to the API at runtime. You would use this if your application has a dialog that lets the user change the opt-in status after an Application Start:
Objective-C Example
//...
[client setOptIn:YES]; //or NO
//...
Swift Example
//...
client.setOptIn(true) //or false
//...
If the opt-in state changes from NO/false to YES/true the API will do an automatic Application Start and analytics will start being gathered. Note: This behavior only occurs if a properly configured Application Start has previously been tried and had failed due to the opt-in status.
There are a couple of things to note about the opt-in state. If the Application Start is skipped because of the opt-in state, the API may still send messages that were previously gathered and stored offline. Once it is done sending that data it will go back to the stopped state. If you change the opt-in state from YES/true to NO/false the API does not immediately shut down. Instead, it stops gathering analytics by ignoring messages, like Feature Ticks et al., but will send any previously created messages. Opt-In does not affect the sending of Application Stop messages if the API was previously started with optIn set to YES/true, or if the API was started by optIn being set to YES/true.
Information About Your Application
There is much more information you can send to the configured endpoint about your application than just its application ID.
- Name
- The name of the application.
- Type
- A user defined application type.
- Version
- A version string for the application. Although no specific format is defined a dotted numeric representation is recommended.
- Instance ID
- An identifier for the instance of the application, such as a serial number. No specific format is defined.
These values along with the Application ID are used to define a particular application. The only value that you should change over time is the Version string.
Objective-C Example
//...
PAConfiguration* config = [[PAConfiguration alloc]
initWithCompanyID:@"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3"
appID:@"21EC2020-AAA1-1069-A2DD-08002B30309D"];
[config setAppName:@"iOS Sample App"];
[config setAppType:@"Sample"];
[config setAppVersion:@"1.0.0"];
[config setAppInstanceID:serialNum];
//Where serialNum is a NSString*. It should be unique to this installation of the app.
//...
Swift Example
//...
let config = PAConfiguration(CompanyID:"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3",
appID:"21EC2020-AAA1-1069-A2DD-08002B30309D")
config.appName="iOS Sample App"
config.appType="Sample"
config.appVersion="1.0.0"
config.appInstanceID=serialNum
//Where serialNum is a String. It should be unique to this installation of the app.
//...
Binary Information
In addition to tracking general application details, you may want to track assembly level details. PABinaryInfo
can be used for this functionality.
Every type of message is capable of sending a PABinaryInfo
object. With this you can track details such as:
- Assembly name and version which sent the message
- Class and Method name which sent the message
- Last modified date on the assembly sending the message
Objective-C Example
-(BOOL) checkRegistration:(NSString*) registrationCode {
PABinaryInfo* info = [[PABinaryInfo alloc] init];
[info setName:@"MyApplication"];
[info setVersion:@"1.23"];
[info setClassName:@"RegistrationDelegate"];
[info setMethodName:@"checkRegistration:"];
[info setModifiedDate:[PAFieldGenerator parseDate:@"2002-08-11T11:11:11.111Z"]];
[client featureTick:@"CheckedRegistration" info:info];
//...
}
Swift Example
func checkRegistration(registrationCode:String) -> Bool {
let info = PABinaryInfo()
info.name="MyApplication"
info.version="1.23"
info.className="RegistrationDelegate"
info.methodName="checkRegistration:"
info.modifiedDate=PAFieldGenerator.parseDate("2002-08-11T11:11:11.111Z")
client.featureTick("CheckedRegistration", info:info)
//...
}
Storing Data Offline
By default the API will try to send messages directly to the configured endpoint. If there is no Internet connection or there is a problem reaching the server it will save the data locally for later transmission.
You can control this behavior with the PAConfiguration
instance before creating the PAClient
. The settings that control the API's behavior are:
- offline
- This indicates that the API will not attempt to send messages to the endpoint (and instead save them to offline storage). By default the API is "online".
- supportOfflineStorage
- This tells the API if it is allowed to store data locally. By default the API will store data locally.
Changing values in the PAConfiguration
instance will not have any effect while an application run is active.
There is also an option called sendDisabled
. An initial value for it can be set on the PAFlowController
instance. This differs from Offline in that it is a temporary condition and can be changed by calling the setSendDisabled
method
on the PAClient
instance while the application run is active. We will talk more about that in Controlling The Transmission Window.
Controlling Data Transmission
The API doesn't send messages immediately to the configured endpoint, instead it bundles them up into groups. The number of message that make up a group is based on a combination of the maximum size of the message queue, how quickly your application
is generating messages, and how long you want to keep messages in memory. All of this behavior is determined by the Flow Controller passed when creating the PAClient
.
Field Size Limits
It is assumed that the server enforces certain size limitations on specific fields. For instance, feature tick names may be truncated to 50 characters by the endpoint because its database is only configured to allow 50 characters. To avoid sending data which will not be utilized and/or may cause problems for the endpoint, the API supports limiting the size of specific XML attributes.
The size limits can be configured by calling the setLimiterType
method that expects one of the following values:
COMMON
-- The lowest limits of the built-in limiters, now and in the futureNONE
-- No limitsPA_TFS
-- For the PreEmptive Analytics for TFS endpointWORKBENCH
-- For the PreEmptive Analytics Workbench endpointRUNTIME_INTELLIGENCE
-- (Deprecated) For the Runtime Intelligence portal
By default, the Common limiter is used for the field size limits. It uses the lowest limit of the built-in limiters. For instance, if one limiter has a company name limit of 100 characters and another a company name limit of 600 characters, the
100 limit will be used to minimize the bandwidth needed. COMMON
combines the limits for the active built-in limiters: PA_TFS
and WORKBENCH
.
Objective-C Example
//...
PAConfiguration* config = [[PAConfiguration alloc]
initWithCompanyID:@"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3"
appID:@"21EC2020-AAA1-1069-A2DD-08002B30309D"];
[config setLimiterType:PA_TFS];
PAClient* client = [[PAClient alloc] initWithConfiguration:config];
//...
Swift Example
//...
let config = PAConfiguration(companyID:"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3",
appID:"21EC2020-AAA1-1069-A2DD-08002B30309D")
config.setLimiterType(PA_TFS)
let client = PAClient(configuration:config)
//...
API Language Interoperability
Analytics can be added to applications created in multiple languages. The PreEmptive Analytics API is available for several languages, but only one can control the Application Start and Stop at a time. The API that starts first controls the scope
of the application run and has APIs implemented in other language run in a subordinate fashion. This is done by setting the defaultSessionID
in the PAConfiguration
class. You can retrieve the generated session GUID by calling
the sessionID
method on PAClient
after it has been started.
Invalid Characters
The API automatically escapes all non-ASCII characters. It also discards characters that are non-escapable according to the XML specification. The API currently does not support characters which require more than 1 word (2 bytes) to represent in UTF-16 (codepoints greater than U+FFFF). These unsupported characters may be sent corrupted or discarded.
The Flow Controller
The PAFlowController
protocol defines the necessary attributes and methods for the flow controller. The default implementation, which is discussed here, is the PADefaultFlowController
. You can implement your own, but it
should behave in a similar fashion.
The first items we will look at in the Flow Controller are those controlling the in-memory queue size. This is where messages are held before being sent to the endpoint or to local storage. There are two controls:
queueSize
- This is the absolute maximum number of messages that will be held in memory. If the queue overflows the oldest messages will be discarded. The default value is 30.
highWater
- This is the point in the queue where the API will try to bundle up the messages for transmission or storage. The space between the highWater and the queueSize gives the API room to handle applications sending messages at a rapid pace. The default value is 20.
When you set the queueSize
the highWater
property is automatically adjusted to be 1/3 less than the size. You may want to adjust these values if your application can send rapid bursts of messages.
The next two items control how often the API will try to flush the queue regardless of how many messages have accumulated.
maximumInterval
- How long, in milliseconds, will a message sit in the queue before the API will attempt to transmit it or store it locally. The default value is 30000.
minimumInterval
- The minimum time, in milliseconds, that the API will wait between queue checks. The default value is 1.
Adjusting these values requires care. Having a maximumInterval
that is too large can cause the API to hang on to memory used to store messages - this can be exacerbated by having a large queueSize
. Setting it too small can
steal time from your application.
The actual time between checks is determined by the PADefaultFlowController
and the rate at which your application is sending messages. This brings us to the next two properties:
gain
- The rate at which the queue-checking interval is adjusted when there are messages in the queue. This is a percentage that controls how quickly the interval will decrease. The interval will never be more frequent than
minimumInterval
. The default is 66.
- The rate at which the queue-checking interval is adjusted when there are messages in the queue. This is a percentage that controls how quickly the interval will decrease. The interval will never be more frequent than
quietGain
- The rate at which the queue checking interval is adjusted when there are no messages in the queue. This value controls how quickly the interval works its way back to
maximumInterval
when there are no queued messages. You can use values from 1 to 100. The default value is 33.
- The rate at which the queue checking interval is adjusted when there are no messages in the queue. This value controls how quickly the interval works its way back to
Finally, there are two properties that let the PADefaultFlowController
handles transmission problems:
maximumSequentialFailureCount
- The
PADefaultFlowController
keeps track of the number of times the endpoint cannot be contacted due to networking issues. Every time the Server is contacted this count is reset. When the count exceeds the set value the network is considered down for a period of time. This keeps the API from trying to use network resources when the chance of success is low. The default value is 3.
- The
retryTimeout
- Once the failure count is reached, the API determines a retry time based on this value. It won't try to send to the configured endpoint again until this timeout has elapsed. The default value is 60 seconds.
Controlling the Transmission Window
The Flow Controller uses the previously mentioned values to adjust how often it will empty the in memory queue of messages. Although this takes place on a background thread with a lower priority it still can happen at a time where your application is doing something critical. The API provides a way to control when the transmission can take place.
If you have places in your application where you need to prevent the API from sending messages you can use the Send Disabled feature:
Objective-C Example
//...
[client setSendDisabled:YES];
// Time critical code here
[client setSendDisabled:NO];
//...
Swift Example
//...
client.setSendDisabled(true)
// Time critical code here
client.setSendDisabled(false)
//...
Another way to use Send Disabled is to only allow sending when your application is in an idle state, for example sitting at a menu screen. In this case you would want to start up the API in the disabled state, which you can do with the PADefaultFlowController
instance:
Objective-C Example
//...
PAConfiguration* config = [[PAConfiguration alloc]
initWithCompanyID:@"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3"
appID:@"21EC2020-AAA1-1069-A2DD-08002B30309D"];
PADefaultFlowController* flowController = [[PADefaultFlowController alloc]
initWithConfiguration:config];
[flowController setSendDisabled:YES];
PAClient* client = [[PAClient alloc] initWithConfiguration:config
flowController:flowController];
[client applicationStart];
// When you enter an idle state
[client setSendDisabled:NO];
// When you are busy again
[client setSendDisabled:YES];
Swift Example
//...
let config = PAConfiguration(companyID:"7D2B02E0-064D-49A0-BC1B-4BE4381C62D3",
appID:"21EC2020-AAA1-1069-A2DD-08002B30309D");
let flowController = PADefaultFlowController(configuration:config)
flowController.sendDisabled=true
let client = PAClient(configuration:config,
flowController:flowController)
client.applicationStart()
// When you enter an idle state
client.setSendDisabled(false)
// When you are busy again
client.setSendDisabled(true)
The last way that you can control the sending of messages is by telling the API that it should flush any queued messages right away. This is done with the sendMessages
method on PAClient
:
Objective-C Example
//...
[client sendMessages];
//...
Swift Example
//...
client.sendMessages()
//...
Note: When sendDisabled
is YES
/true
, messages will be kept in the queue. If the queue reaches the max limit (as defined by in the PAFlowController
), messages will be dropped. Calling sendMessages
will
process the queue regardless of the current sendDisabled
setting.
Time To Live
If you send many messages and it's a fairly rare occurrence that the user is connected to the Internet, then it's possible that messages could consume an ever-increasing amount of disk space. The API addresses this problem by exposing a set of criteria that control how old messages may be deleted. There are two properties to configure this behavior:
minimumFailureCount
- The minimum number of times a message will attempt to be sent. The default is 150 times
minimumTimeToLive
- The minimum amount of seconds a message must exist before being eligible for deletion. The default is
259200
(3 days).
- The minimum amount of seconds a message must exist before being eligible for deletion. The default is
Both of these conditions must be met before a message is eligible for deletion.
These properties are also retroactive. So, if you were to set these to very high values that impacted a user's experience(by using significant disk space), you could publish an update to your application with lower values for these properties and old messages qualifying with the new properties would be deleted.
For ease of use, these properties exist in both PAConfiguration
and PAFlowController
. See Interactions With Configuration for more details about this.
Batch Size Limits
Most servers have limitations on how large of a request they can process. Because of this, the API has support for splitting requests that would surpass the server's limits. If a single message is larger than this size, it will be discarded (if transmission fails).
This is configurable by using the maximumBatchSize
property on the PAConfiguration
or the PAFlowController
instance. By default it is set to 4Mb - 4Kb
. We leave 4K of extra space so that there is plenty
of room for HTTP headers
Interactions With Configuration
It's not expected that most people will need all of the control that creating a PAFlowController
allows. Because of this, many properties are duplicated between PAConfiguration
and PAFlowController
, including:
offline
supportOfflineStorage
minimumTimeToLive
minimumFailureCount
maximumBatchSize
This is so that creating a new PAFlowController
instance isn't normally required for more common configuration options. If you do create a new PADefaultFlowController
instance however, then the properties from the PAConfiguration
instance are not used for
these duplicated values. This is because the API also allows the creation of a custom flow controller using the PAFlowController
interface, which is disconnected from the default implementation and has these properties exposed at
a more abstract level. You can initialize the PADefaultFlowController with the configuration to use those properties by using constructing a PADefaultFlowController
with the PAConfiguration
.
Here is an example of this behavior:
Objective-C Example
//...
[config setOffline:YES];
PADefaultFlowController* flowController = [[PADefaultFlowController alloc]
initWithConfiguration:config];
//flowController is initialized with offline being YES.
[flowController setOffline:NO];//This is the actual value that will be used!
PAClient* client = [[PAClient alloc] initWithConfiguration:config
flowController:flowController];
[client applicationStart];
//the API will be "online" at this point, not "offline"
Swift Example
//...
config.offline=true
let flowController = PADefaultFlowController(configuration:config)
//flowController is initialized with offline being YES.
flowController.offline=false//This is the actual value that will be used!
let client = PAClient(configuration:config,
flowController:flowController)
client.applicationStart()
//the API will be "online" at this point, not "offline"
Client-Side Logging
The API has the ability to do client-side logging for debugging purposes. This is accomplished by setting the loglevel
in PAConfiguration
.
Objective-C Example
//...
[config setLogLevel:INFO_LEVEL];
PAClient* client = [[PAClient alloc] initWithConfiguration:config];
[client applicationStart];
//...
Swift Example
//...
config.logLevel=INFO_LEVEL
let client = PAClient(configuration:config)
client.applicationStart()
//...
The provided logger uses NSLog() to output the logging information.