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:

  1. Open your Project Navigator.
  2. Choose the target.
  3. Go to the Build Phases tab.
  4. Expand the Link Binary With Libraries section.
  5. Click the +.
  6. Select Add Other....
  7. Browse to PA_API.framework and choose Open.

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.

  1. Add SwiftPAHeader.h to your project. (Make sure "Copy If Needed" is selected)
  2. Open your Project Navigator.
  3. Choose the target.
  4. Go to Build Settings tab.
  5. Expand the Swift Compiler - Code Generation section.
  6. Set the Objective C Bridging Header to include the project-relative path for SwiftPAHeader.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:

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:

  1. A Company ID
  2. 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:

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:

  1. Thread A calls client.featureStart("foo")
  2. Thread B calls client.featureStart("foo")
  3. Thread A calls client.featureStop("foo")
  4. 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:

  1. Thread A calls client.featureStart("foo")
  2. 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 starts 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:

The Omit Personal Info (PII) setting affects the data reported in the profile:

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 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:

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:

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.

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:

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:

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:

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:

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.

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:

Finally, there are two properties that let the PADefaultFlowController handles transmission problems:

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:

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:

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.


iOS API Version 2.0.0. Copyright © 2015 PreEmptive Solutions, LLC