---
source_url: https://www.pubnub.com/docs/sdks/objective-c/api-reference/storage-and-playback
title: Message Persistence API for Objective-C SDK
updated_at: 2026-06-16T12:51:51.141Z
sdk_name: PubNub Objective-C SDK
sdk_version: 7.0.3
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Message Persistence API for Objective-C SDK

PubNub Objective-C SDK, use the latest version: 7.0.3

Install:

```bash
pod install PubNub@7.0.3
```

Message Persistence gives you real-time access to the history of messages published to PubNub. Each message is timestamped to the nearest 10 nanoseconds and stored across multiple availability zones in several geographic locations. You can encrypt stored messages with AES-256 so they are not readable on PubNub’s network. For details, see [Message Persistence](https://www.pubnub.com/docs/general/storage).

You control how long messages are stored through your account’s retention policy. Options include: 1 day, 7 days, 30 days, 3 months, 6 months, 1 year, or Unlimited.

You can retrieve the following:

* Messages
* Message reactions
* Files (using the File Sharing API)

## Fetch history

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Fetch historical messages from one or more channels. Use `includeMessageActions` to include message actions.

You can control how messages are returned and in what order.

* If you specify only the `start` parameter (without `end`), you receive messages older than the `start` timetoken.
* If you specify only the `end` parameter (without `start`), you receive messages from that `end` timetoken and newer.
* If you specify both `start` and `end`, you retrieve messages between those timetokens (inclusive of the `end` value).

You can receive up to 100 messages for a single channel. For multiple channels (up to 500), you can receive up to 25 messages per channel. If more messages match the time range, make iterative calls and adjust the `start` timetoken to page through the results.

### Method(s)

Use the following method(s) in the Objective-C SDK:

```objectivec
history()
    .channels(NSArray *)
    .start(NSNumber *)
    .end(NSNumber *)
    .limit(NSUInteger)
    .reverse(BOOL)
    .includeMetadata(BOOL)
    .includeMessageType(BOOL)
    .includeCustomMessageType(BOOL)
    .includeUUID(BOOL)
    .includeMessageActions(BOOL)
    .includeTimeToken(BOOL)
    .performWithCompletion(PNHistoryCompletionBlock);
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channels | NSString | Yes |  | List of channels to fetch history messages from (up to 500). |
| start | NSNumber | Optional |  | Timetoken delimiting the start (exclusive) of the time slice. |
| end | NSNumber | Optional |  | Timetoken delimiting the end (inclusive) of the time slice. |
| limit | NSUInteger | Optional |  | Number of historical messages to return per channel. Default and maximum are 100 (single), 25 (multi), and 25 with `includeMessageActions`. |
| reverse | BOOL | Optional |  | Whether to traverse from oldest to newest. |
| includeMetadata | BOOL | Optional |  | Whether to include metadata in the response. |
| includeMessageType | BOOL | Optional |  | Whether to include message type. Default is `YES`. |
| includeCustomMessageType | BOOL | Optional |  | Whether to include custom message type. Default is `YES`. See [Retrieving Messages](https://www.pubnub.com/docs/general/storage#retrieve-messages). |
| includeUUID | BOOL | Optional |  | Whether to include the publisher uuid. Default is `YES`. |
| includeMessageActions | BOOL | Optional |  | Whether to include message actions in the response. If `YES`, only one channel is allowed. |
| includeTimeToken | BOOL | Optional |  | Whether to include event timetokens in the response. |
| block | PNHistoryCompletionBlock | Yes |  | History completion block. |

:::tip
Using the
reverse
parameter
Messages are always returned sorted in ascending time direction from history regardless of `reverse`. The `reverse` direction matters when you have more than 100 (or `limit`, if it's set) messages in the time interval, in which case `reverse` determines the end of the time interval from which it should start retrieving the messages.
:::

### Sample code

```objectivec
#import <Foundation/Foundation.h>
#import <PubNub/PubNub.h>

// Basic configuration
PNConfiguration *config = [PNConfiguration configurationWithPublishKey:@"demo"
                                                          subscribeKey:@"demo"
                                                                userID:@"historyUser"];

// Create a PubNub client instance
PubNub *client = [PubNub clientWithConfiguration:config];

// Add listener for PubNub events
[client addListener:self];

// Publish sample messages for history demo
NSLog(@"Publishing sample messages for history demo...");

// Publish several messages with different content and metadata
NSArray *messages = @[
    @{@"text": @"First message", @"id": @1},
    @{@"text": @"Second message", @"id": @2},
    @{@"text": @"Third message", @"id": @3},
    @{@"text": @"Fourth message", @"id": @4},
    @{@"text": @"Fifth message", @"id": @5}
];

// Define metadata for the messages
NSArray *metadata = @[
    @{@"type": @"text", @"importance": @"low"},
    @{@"type": @"text", @"importance": @"medium"},
    @{@"type": @"text", @"importance": @"high"},
    @{@"type": @"text", @"importance": @"medium"},
    @{@"type": @"text", @"importance": @"low"}
];

// Publish messages to a history demo channel
for (NSUInteger i = 0; i < messages.count; i++) {
    PNPublishRequest *request = [PNPublishRequest requestWithChannel:@"history-demo"];
    request.message = messages[i];
    request.metadata = metadata[i];

    [client publishWithRequest:request withCompletion:^(PNPublishStatus *status) {
        if (!status.isError) {
            NSLog(@"✅ Published message %lu for history demo", (unsigned long)(i + 1));
        } else {
            NSLog(@"❌ Failed to publish message %lu: %@", (unsigned long)(i + 1), 
                 status.errorData.information);
        }
    }];
}

// Publish messages to a second channel for multi-channel history demo
for (NSUInteger i = 0; i < 3; i++) {
    PNPublishRequest *request = [PNPublishRequest requestWithChannel:@"history-demo-2"];
    request.message = @{@"text": [NSString stringWithFormat:@"Channel 2 - Message %lu", (unsigned long)(i + 1)]};

    [client publishWithRequest:request withCompletion:^(PNPublishStatus *status) {
        if (!status.isError) {
            NSLog(@"✅ Published message to second channel for history demo");
        }
    }];
}

// Example 1: Basic history fetch with limit
NSLog(@"Fetching basic history...");

PNHistoryFetchRequest *basicRequest = [PNHistoryFetchRequest requestWithChannels:@[@"history-demo"]];
basicRequest.limit = 3;

[client fetchHistoryWithRequest:basicRequest completion:^(PNHistoryResult *result, PNErrorStatus *status) {
    if (!status.isError) {
        NSLog(@"✅ Basic history fetch successful!");
        NSLog(@"Messages: %@", result.data.messages);
    } else {
        NSLog(@"❌ Error fetching basic history: %@", status.errorData.information);
    }
}];

// Example 2: History fetch with timeframe
NSLog(@"Fetching history with timeframe...");

PNHistoryFetchRequest *timeframeRequest = [PNHistoryFetchRequest requestWithChannels:@[@"history-demo"]];
timeframeRequest.start = @(17457898826964534);  // Example start time
timeframeRequest.end = @(17457898826964534);    // Example end time

[client fetchHistoryWithRequest:timeframeRequest completion:^(PNHistoryResult *result, PNErrorStatus *status) {
    if (!status.isError) {
        NSLog(@"✅ History fetch with timeframe successful!");
        NSLog(@"Messages: %@", result.data.messages);
    } else {
        NSLog(@"❌ Error fetching history with timeframe: %@", status.errorData.information);
    }
}];

// Example 3: History fetch with metadata
NSLog(@"Fetching history with metadata...");

PNHistoryFetchRequest *metadataRequest = [PNHistoryFetchRequest requestWithChannels:@[@"history-demo"]];
metadataRequest.includeMetadata = YES;

[client fetchHistoryWithRequest:metadataRequest completion:^(PNHistoryResult *result, PNErrorStatus *status) {
    if (!status.isError) {
        NSLog(@"✅ History fetch with metadata successful!");
        NSLog(@"Messages with metadata: %@", result.data.messages);
    } else {
        NSLog(@"❌ Error fetching history with metadata: %@", status.errorData.information);
    }
}];

// Example 4: History fetch with message actions
NSLog(@"Fetching history with message actions...");

PNHistoryFetchRequest *actionsRequest = [PNHistoryFetchRequest requestWithChannels:@[@"history-demo"]];
actionsRequest.includeMessageActions = YES;

[client fetchHistoryWithRequest:actionsRequest completion:^(PNHistoryResult *result, PNErrorStatus *status) {
    if (!status.isError) {
        NSLog(@"✅ History fetch with message actions successful!");
        NSLog(@"Messages with actions: %@", result.data.messages);
    } else {
        NSLog(@"❌ Error fetching history with message actions: %@", status.errorData.information);
    }
}];

// Example 5: History fetch from multiple channels
NSLog(@"Fetching history from multiple channels...");

PNHistoryFetchRequest *multiChannelRequest = [PNHistoryFetchRequest requestWithChannels:@[@"history-demo", @"history-demo-2"]];
multiChannelRequest.limit = 2;  // Limit per channel

[client fetchHistoryWithRequest:multiChannelRequest completion:^(PNHistoryResult *result, PNErrorStatus *status) {
    if (!status.isError) {
        NSLog(@"✅ Multi-channel history fetch successful!");
        [result.data.channels enumerateKeysAndObjectsUsingBlock:^(NSString *channel, NSDictionary *messages, BOOL *stop) {
            NSLog(@"Messages from %@: %@", channel , messages);
        }];
    } else {
        NSLog(@"❌ Error fetching multi-channel history: %@", status.errorData.information);
    }
}];

// Required PNEventsListener methods
- (void)client:(PubNub *)client didReceiveStatus:(PNStatus *)status {
    // Checking connectivity only using subscribe operation.
    if (status.operation != PNSubscribeOperation) return;
    
    if (status.category == PNConnectedCategory) {
        NSLog(@"✅ Successfully connected to PubNub!");
    } else if (status.isError) {
        NSLog(@"❌ PubNub connection error: %@", status);
    }
}

- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {
    NSLog(@"Received message: %@ on channel: %@", message.data.message, message.data.channel);
}
```

### Response

Response objects which is returned by client when `fetch messages` Message Persistence API is used:

```objectivec
@interface PNHistoryData : PNServiceData

/**
 * Channel history messages.
 *
 * Set only for PNHistoryOperation operation and will be empty array for other operation types.
 */
@property (nonatomic, readonly, strong) NSArray *messages;

/**
 * Channels history.
 *
 * Each key represent name of channel for which messages has been received and values is list of
 * messages from channel's storage.
 *
 * For PNHistoryOperation operation this property always will be empty dictionary.
 */
@property (nonatomic, readonly, strong) NSDictionary<NSString *, NSArray *> *channels;

/**
 * Fetched history time frame start time.
 *
 * Set only for PNHistoryOperation operation and will be 0 for other operation types.
 */
@property (nonatomic, readonly, strong) NSNumber *start;

/**
 * Fetched history time frame end time.
 *
 * Set only for PNHistoryOperation operation and will be 0 for other operation types.
 */
@property (nonatomic, readonly, strong) NSNumber *end;

@end

@interface PNHistoryResult : PNResult

// Fetch history request processed information.
@property (nonatomic, readonly, strong) PNHistoryData *data;

@end
```

Error response which is used in case of Message Persistence API call failure:

```objectivec
@interface PNErrorData : PNServiceData

// Stringified error information.
@property (nonatomic, readonly, strong) NSString *information;

@end

@interface PNErrorStatus : PNStatus

// Whether status object represent error or not.
@property (nonatomic, readonly, assign, getter = isError) BOOL error;

// Additional information related to error status object.
@property (nonatomic, readonly, strong) PNErrorData *errorData;

@end
```

### Other examples

#### Fetch messages with metadata

```objectivec
self.client.history()
    .channel(@"storage")
    .includeMetadata(YES)
    .performWithCompletion(^(PNHistoryResult *result, PNErrorStatus *status) {
        if (!status.isError) {
            /**
            * Fetched data available here:
            *   result.data.channels - dictionary with single key (name of requested channel) and
            *       list of dictionaries as value. Each entry will include two keys: "message" - for
            *       body and "metadata" for meta which has been added during message publish.
            */
        } else {
            /**
            * Handle message history download error. Check 'category' property to find out possible
            * issue because of which request did fail.
            *
            * Request can be resent using: [status retry];
            */
        }
    });
```

#### Fetch messages with actions

```objectivec
self.client.history()
    .channel(@"chat")
    .includeMessageActions(YES)
    .performWithCompletion(^(PNHistoryResult *result, PNErrorStatus *status) {
        if (!status.isError) {
            /**
            * Fetched data available here:
            *   result.data.channels - dictionary with single key (name of requested channel) and
            *       list of dictionaries. Each entry will include two keys: "message" - for body and
            *       "actions" for list of added actions.
            */
        } else {
            /**
            * Handle message history download error. Check 'category' property to find out possible
            * issue because of which request did fail.
            *
            * Request can be resent using: [status retry];
            */
        }
    });
```

#### Fetch messages with metadata and actions

```objectivec
self.client.history()
    .channel(@"chat")
    .includeMetadata(YES)
    .includeMessageActions(YES)
    .performWithCompletion(^(PNHistoryResult *result, PNErrorStatus *status) {
        if (!status.isError) {
            /**
            * Fetched data available here:
            *   result.data.channels - dictionary with single key (name of requested channel) and
            *       list of dictionaries. Each entry will include three keys: "message" - for body,
            *       "metadata" for meta which has been added during message publish and "actions"
            *       for list of added actions.
            */
        } else {
            /**
            * Handle message history download error. Check 'category' property to find out possible
            * issue because of which request did fail.
            *
            * Request can be resent using: [status retry];
            */
        }
    });
```

## Delete messages from history

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Remove messages from the history of a specific channel.

:::note Required setting
Enable `Delete-From-History` for your key in the Admin Portal and initialize the SDK with a secret key.
:::

### Method(s)

To `Delete Messages from History` you can use the following method(s) in the Objective-C SDK.

```objectivec
- (void)deleteMessagesFromChannel:(NSString *)channel 
                            start:(nullable NSNumber *)startDate 
                              end:(nullable NSNumber *)endDate 
                   withCompletion:(nullable PNMessageDeleteCompletionBlock)block;
```

### Sample code

```objectivec
[self.client deleteMessagesFromChannel:@"channel" start:@15101397027611671 end:@15101397427611671
                        withCompletion:^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
        // Messages within specified time frame has been removed.
    } else {
       /**
        * Handle message history download error. Check 'category' property to find out possible
        * issue because of which request did fail.
        *
        * Request can be resent using: [status retry];
        */
    }
}];
```

### Other examples

#### Delete specific message from history

To delete a specific message, pass the `publish timetoken` (received from a successful publish) in the `End` parameter and `timetoken +/- 1` in the `Start` parameter. e.g. if `15526611838554310` is the `publish timetoken`, pass `15526611838554309` in `Start` and `15526611838554310` in `End` parameters respectively as shown in the following code snippet.

```objectivec
[self.client deleteMessagesFromChannel:@"channel" start:@15526611838554309 end:@15526611838554310
                        withCompletion:^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
        // Messages within specified time frame has been removed.
    } else {
        /**
        * Handle message history download error. Check 'category' property to find out possible
        * issue because of which request did fail.
        *
        * Request can be resent using: [status retry];
        */
    }
}];
```

## Delete messages from history (builder pattern)

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Remove messages from the history of a specific channel.

:::note Required setting
Enable `Delete-From-History` for your key in the Admin Portal and initialize the SDK with a secret key.
:::

### Method(s)

To `Delete Messages from History` you can use the following method(s) in the Objective-C SDK.

```objectivec
deleteMessage()
    .channel(NSString *)
    .start(NSNumber *)
    .end(NSNumber *)
    .performWithCompletion(PNAcknowledgmentStatus *);
```

### Sample code

```objectivec
self.client.deleteMessage()
    .channel(@"channel")
    .start(@15101397027611671)
    .end(@15101397427611671)
    .performWithCompletion(^(PNAcknowledgmentStatus *status) {

    if (!status.isError) {
        // Messages within specified time frame has been removed.
    } else {
       /**
        * Handle message history download error. Check 'category' property to find out possible
        * issue because of which request did fail.
        *
        * Request can be resent using: [status retry];
        */
    }
});
```

## Message counts

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

Return the number of messages published since the given time. The count is the number of messages with a timetoken greater than or equal to the value in `timetokens`.

:::note Unlimited message retention
Only messages from the last 30 days are counted.
:::

### Method(s)

You can use the following method(s) in the Objective-C SDK:

```objectivec
messageCounts()
    .channels(NSArray<NSString *> *)
    .timetokens(NSArray<NSNumber *> *)
    .performWithCompletion(PNMessageCountCompletionBlock);
```

| Parameter | Description |
| --- | --- |
| `channels` *Type: NSArray`<NSString *>` *Default: n/a | The `channels` to fetch the message count |
| `timetokens` *Type: NSArray`<NSNumber *>` *Default: n/a | List with single or multiple timetokens, where each timetoken position in correspond to target `channel` location in channel names list. |
| `completion` *Type: PNMessageCountCompletionBlockDefault: n/a | Messages count fetch completion closure which pass two arguments: `result` - in case of successful request processing data field will contain results of message count fetch operation; `status` - in case of error occurred during request processing. |

### Sample code

```objectivec
self.client.messageCounts().channels(@[@"unread-channel-1", @"unread-channel-2"])
    .timetokens(@[@(15501015683744028)])
    .performWithCompletion(^(PNMessageCountResult *result, PNErrorStatus *status) {

        if (!status.isError) {
            // Client state retrieved number of messages for channels.
        } else {
            /**
             Handle client state modification error. Check 'category' property
             to find out possible reason because of which request did fail.
             Review 'errorData' property (which has PNErrorData data type) of status
             object to get additional information about issue.

             Request can be resent using: [status retry]
            */
        }
    });
```

### Returns

:::note
`Channels` without messages have a count of 0. `Channels` with 10,000 messages or more have a count of 10000.
:::

```objectivec
@interface PNMessageCountData : PNServiceData

/**
 * @brief Dictionary where each key is name of channel and value is number of messages in it.
 */
@property (nonatomic, readonly, strong) NSDictionary<NSString *, NSNumber *> *channels;

@end

@interface PNMessageCountResult : PNResult

/**
 * @brief Message count request processing information.
 */
@property (nonatomic, readonly, strong) PNMessageCountData *data;

@end
```

### Other examples

#### Retrieve count of messages using different timetokens for each channel

```objectivec
self.client.messageCounts().channels(@[@"unread-channel-1", @"unread-channel-2"])
    .timetokens(@[@(15501015683744028), @(15501015683744130)])
    .performWithCompletion(^(PNMessageCountResult *result, PNErrorStatus *status) {

        if (!status.isError) {
            // Client state retrieved number of messages for channels.
        } else {
            /**
                Handle client state modification error. Check 'category' property
                to find out possible reason because of which request did fail.
                Review 'errorData' property (which has PNErrorData data type) of status
                object to get additional information about issue.

                Request can be resent using: [status retry]
            */
        }
    });
```

## History (deprecated)

:::warning Requires Message Persistence
Enable Message Persistence for your key in the [Admin Portal](https://admin.pubnub.com/). See how to [enable add-on features](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-).
:::

:::note Alternative method
This method is deprecated. Use [fetch history](#fetch-history) instead.
:::

This function fetches historical messages of a channel.

It is possible to control how messages are returned and in what order, for example you can:

* Search for messages starting on the newest end of the timeline (default behavior - `reverse` = `NO`)
* Search for messages from the oldest end of the timeline by setting `reverse` to `YES`.
* Page through results by providing a `start` OR `end` timetoken.
* Retrieve a *slice* of the time line by providing both a `start` AND `end` timetoken.
* Limit the number of messages to a specific quantity using the `limit` parameter.

:::tip Start & End parameter usage clarity
If only the `start` parameter is specified (without `end`), you receive messages that are older than and up to that `start` timetoken value. If only the `end` parameter is specified (without `start`) you receive messages that match that `end` timetoken value and newer. Specifying values for both `start` and `end` parameters returns messages between those timetoken values (inclusive on the `end` value). Keep in mind that you still receive a maximum of 100 messages even if more messages meet the timetoken values. Iterative calls to history adjusting the `start` timetoken are necessary to page through the full set of results if more than 100 messages meet the timetoken values.
:::

### Method(s)

To run `History` you can use the following method(s) in the Objective-C SDK:

```objectivec
- (void)historyForChannel:(NSString *)channel 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `block` *Type: PNHistoryCompletionBlock | `History` pull processing completion `block` which pass two arguments: result - in case of successful request processing data field will contain results of `history` request operation; status - in case if error occurred during request processing. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
             withMetadata:(BOOL)shouldIncludeMetadata 
               completion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `shouldIncludeMetadata` *Type: BOOL | Whether event metadata should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull completion `block`. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
       withMessageActions:(BOOL)shouldIncludeMessageActions 
               completion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `shouldIncludeMessageActions` *Type: BOOL | Whether event actions should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull completion `block`. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
             withMetadata:(BOOL)shouldIncludeMetadata 
           messageActions:(BOOL)shouldIncludeMessageActions 
               completion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `shouldIncludeMetadata` *Type: BOOL | Whether event metadata should be included in response or not. |
| `shouldIncludeMessageActions` *Type: BOOL | Whether event actions should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull completion `block`. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Reference on timetoken for the oldest event (exclusive) to start pulling messages from. |
| `endDate`Type: NSNumber | Reference on timetoken for the latest event (inclusive) to stop pulling messages at. |
| `block` *Type: PNHistoryCompletionBlock | History pull processing completion `block` which pass two arguments: result - in case of successful request processing data field will contain results of history request operation; status - in case if error occurred during request processing. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
          includeMetadata:(BOOL)shouldIncludeMetadata 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Timetoken for oldest event starting from which next should be returned events. Value will be converted to required precision internally. |
| `endDate`Type: NSNumber | Timetoken for latest event till which events should be pulled out. Value will be converted to required precision internally. |
| `shouldIncludeMetadata` *Type: BOOL | Whether event metadata should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull completion `block`. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
    includeMessageActions:(BOOL)shouldIncludeMessageActions 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Timetoken for oldest event starting from which next should be returned events. Value will be converted to required precision internally. |
| `endDate`Type: NSNumber | Timetoken for latest event till which events should be pulled out. Value will be converted to required precision internally. |
| `shouldIncludeMessageActions` *Type: BOOL | Whether event actions should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull completion `block`. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
                    limit:(NSUInteger)limit 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Reference on timetoken for the oldest event (exclusive) to start pulling messages from. |
| `endDate`Type: NSNumber | Reference on timetoken for the latest event (inclusive) to stop pulling messages at. |
| `limit` *Type: NSUInteger | Maximum number of events to return (not more than 100). |
| `shouldReverseOrder` *Type: Bool | Whether to reverse the order (oldest to newest). |
| `shouldIncludeTimeToken` *Type: Bool | Whether to include timetokens in the response. |
| `block` *Type: PNHistoryCompletionBlock | History pull completion block. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
         includeTimeToken:(BOOL)shouldIncludeTimeToken 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Reference on timetoken for the oldest event (exclusive) to start pulling messages from. |
| `endDate`Type: NSNumber | Reference on timetoken for the latest event (inclusive) to stop pulling messages at. |
| `shouldIncludeTimeToken` *Type: Bool | Whether to include timetokens in the response. |
| `block` *Type: PNHistoryCompletionBlock | History pull processing completion `block` which pass two arguments: result - in case of successful request processing data field will contain results of history request operation;, status - in case if error occurred during request processing. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
                    limit:(NSUInteger)limit 
         includeTimeToken:(BOOL)shouldIncludeTimeToken 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Reference on timetoken for oldest event starting from which next should be returned events. |
| `endDate`Type: NSNumber | Reference on timetoken for latest event till which events should be pulled out. |
| `limit` *Type: NSUInteger | Maximum number of events which should be returned in response (not more then 100). |
| `shouldIncludeTimeToken` *Type: Bool | Whether event dates (timetokens) should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull processing completion `block` which pass two arguments: result - in case of successful request processing data field will contain results of history request operation; status - in case if error occurred during request processing. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
                    limit:(NSUInteger)limit 
                  reverse:(BOOL)shouldReverseOrder 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Reference on timetoken for oldest event starting from which next should be returned events. |
| `endDate`Type: NSNumber | Reference on timetoken for latest event till which events should be pulled out. |
| `limit` *Type: NSUInteger | Maximum number of events which should be returned in response (not more then 100). |
| `shouldReverseOrder` *Type: Bool | Whether events order in response should be reversed or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull processing completion `block` which pass two arguments: result - in case of successful request processing data field will contain results of history request operation;, status - in case if error occurred during request processing. |

```objectivec
- (void)historyForChannel:(NSString *)channel 
                    start:(nullable NSNumber *)startDate 
                      end:(nullable NSNumber *)endDate 
                    limit:(NSUInteger)limit 
                  reverse:(BOOL)shouldReverseOrder 
         includeTimeToken:(BOOL)shouldIncludeTimeToken 
           withCompletion:(PNHistoryCompletionBlock)block;
```

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Reference on timetoken for oldest event starting from which next should be returned events. |
| `endDate`Type: NSNumber | Reference on timetoken for latest event till which events should be pulled out. |
| `limit` *Type: NSUInteger | Maximum number of events which should be returned in response (not more then 100). |
| `shouldReverseOrder` *Type: Bool | Whether events order in response should be reversed or not. |
| `shouldIncludeTimeToken` *Type: Bool | Whether event dates (timetokens) should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull processing completion `block` which pass two arguments: result - in case of successful request processing data field will contain results of history request operation; status - in case if error occurred during request processing. |

:::tip
Using the
reverse
parameter
Messages are always returned sorted in ascending time direction from history regardless of `reverse`. The `reverse` direction matters when you have more than 100 (or `limit`, if it's set) messages in the time interval, in which case `reverse` determines the end of the time interval from which it should start retrieving the messages.
:::

### Sample code

Retrieve the last 100 messages on a channel:

```objectivec
[self.client historyForChannel: @"my_channel" start:nil end:nil limit:100
                withCompletion:^(PNHistoryResult *result, PNErrorStatus *status) {

    if (!status) {

        /**
         Handle downloaded history using:
            result.data.start - oldest message time stamp in response
            result.data.end - newest message time stamp in response
            result.data.messages - list of messages
         */
    }
    else {

        /**
         Handle message history download error. Check 'category' property
         to find out possible reason because of which request did fail.
         Review 'errorData' property (which has PNErrorData data type) of status
         object to get additional information about issue.

         Request can be resent using: [status retry];
         */
    }
}];
```

### Response

The response object which is returned by the client when the history API is used:

```objectivec
@interface PNHistoryData : PNServiceData

// Channel history messages.
@property (nonatomic, readonly, strong) NSArray *messages;
// History time frame start time.
@property (nonatomic, readonly, strong) NSNumber *start;
// History time frame end time.
@property (nonatomic, readonly, strong) NSNumber *end;

@end

@interface PNHistoryResult : PNResult

// Stores reference on channel history request processing information.
@property (nonatomic, readonly, strong) PNHistoryData *data;

@end
```

### Other examples

#### Use historyForChannel to retrieve the three oldest messages by retrieving from the time line in reverse

```objectivec
[self.client historyForChannel:@"my_channel" start:nil end:nil limit:3 reverse:YES
                withCompletion:^(PNHistoryResult *result, PNErrorStatus *status) {

    if (!status) {

        /**
            Handle downloaded history using:
            result.data.start - oldest message time stamp in response
            result.data.end - newest message time stamp in response
            result.data.messages - list of messages
            */
    }
    else {

        /**
            Handle message history download error. Check 'category' property
            to find out possible reason because of which request did fail.
            Review 'errorData' property (which has PNErrorData data type) of status
            object to get additional information about issue.

            Request can be resent using: [status retry];
            */
    }
}];
```

##### Response

```json
[
    ["Pub1","Pub2","Pub3"],
    13406746729185766,
    13406746780720711
]
```

#### Use historyForChannel to retrieve messages newer than a given timetoken by paging from oldest message to newest message starting at a single point in time (exclusive)

```objectivec
[self.client historyForChannel:@"my_channel" start:@(13406746780720711) end:nil limit:100
                        reverse:YES withCompletion:^(PNHistoryResult *result, PNErrorStatus *status) {

    if (!status) {

        /**
            Handle downloaded history using:
            result.data.start - oldest message time stamp in response
            result.data.end - newest message time stamp in response
            result.data.messages - list of messages
            */
    }
    else {

        /**
            Handle message history download error. Check 'category' property
            to find out possible reason because of which request did fail.
            Review 'errorData' property (which has PNErrorData data type) of status
            object to get additional information about issue.

            Request can be resent using: [status retry];
            */
    }
}];
```

##### Response

```json
[
    ["Pub3","Pub4","Pub5"],
    13406746780720711,
    13406746845892666
]
```

#### Use historyForChannel to retrieve messages until a given timetoken by paging from newest message to oldest message until a specific end point in time (inclusive)

```objectivec
[self.client historyForChannel:@"my_channel" start:nil end:@(13406746780720711) limit:100
                includeTimeToken:NO withCompletion:^(PNHistoryResult *result, PNErrorStatus *status) {

    if (!status) {

        /**
            Handle downloaded history using:
            result.data.start - oldest message time stamp in response
            result.data.end - newest message time stamp in response
            result.data.messages - list of messages
            */
    }
    else {

        /**
            Handle message history download error. Check 'category' property
            to find out possible reason because of which request did fail.
            Review 'errorData' property (which has PNErrorData data type) of status
            object to get additional information about issue.

            Request can be resent using: [status retry];
            */
    }
}];
```

##### Response

```json
[
    ["Pub3","Pub4","Pub5"],
    13406746780720711,
    13406746845892666
]
```

#### History paging example

:::note Usage
You can call the method by passing 0 or a valid **timetoken** as the argument.
:::

```objectivec
// Pull out all messages newer than message sent at 14395051270438477.
[self historyFromStartDate:@(14395051270438477) onChannel:@"history_channel"
        withCompletionBlock:^(NSArray *messages) {

    NSLog(@"Messages from history: %@", messages);
}];

- (void)historyNewerThan:(NSNumber *)beginTime onChannel:(NSString *)channelName
        withCompletionBlock:(void (^)(NSArray *messages))block {

    NSMutableArray *msgs = [NSMutableArray new];
    [self historyFromStartDate:beginTime onChannel:channelName
                    withProgress:^(NSArray *objects) {

        [msgs addObjectsFromArray:objects];
        if (objects.count < 100) { block(msgs); }
    }];
}

- (void)historyFromStartDate:(NSNumber *)beginTime onChannel:(NSString *)channelName
                withProgress:(void (^)(NSArray *objects))block {

    __weak __typeof(self) weakSelf = self;
    [self.client historyForChannel:channelName start:beginTime end:nil limit:100
                            reverse:NO includeTimeToken:YES
                    withCompletion:^(PNHistoryResult *result, PNErrorStatus *status) {

        __strong __typeof__(weakSelf) strongSelf = weakSelf;
        if (!status) {

            block(result.data.messages);
            if ([result.data.messages count] == 100) {

                [strongSelf historyFromStartDate:result.data.start onChannel:channelName
                                    withProgress:block];
            }
        }
        else {

            /**
                Handle message history download error. Check the 'category' property
                to find out why the request failed.
                Review the 'errorData' property (of type PNErrorData) of the status
                object to get additional information about the issue.

                Request can be resent using: [status retry];
            */
        }
    }];
}
```

#### Fetch messages with metadata

```objectivec
[self.client historyForChannel:@"storage" withMetadata:YES
                    completion:^(PNHistoryResult *result, PNErrorStatus *status) {

    if (!status.isError) {
        /**
        * Fetched data available here:
        *   result.data.channels - dictionary with single key (name of requested channel) and
        *       list of dictionaries as value. Each entry will include two keys: "message" - for
        *       body and "metadata" for meta which has been added during message publish.
        */
    } else {
        /**
        * Handle message history download error. Check 'category' property to find out possible
        * issue because of which request did fail.
        *
        * Request can be resent using: [status retry];
        */
    }
}];
```

#### Fetch messages with actions

```objectivec
[self.client historyForChannel:@"chat" withMessageActions:YES
                    completion:^(PNHistoryResult *result, PNErrorStatus *status) {

    if (!status.isError) {
        /**
        * Fetched data available here:
        *   result.data.channels - dictionary with single key (name of requested channel) and
        *       list of dictionaries. Each entry will include two keys: "message" - for body and
        *       "actions" for list of added actions.
        */
    } else {
        /**
        * Handle message history download error. Check 'category' property to find out possible
        * issue because of which request did fail.
        *
        * Request can be resent using: [status retry];
        */
    }
}];
```

#### Fetch messages with metadata and actions

```objectivec
[self.client historyForChannel:@"chat" withMetadata:YES messageActions:YES
                    completion:^(PNHistoryResult *result, PNErrorStatus *status) {

    if (!status.isError) {
        /**
        * Fetched data available here:
        *   result.data.channels - dictionary with single key (name of requested channel) and
        *       list of dictionaries. Each entry will include three keys: "message" - for body,
        *       "metadata" for meta which has been added during message publish and "actions"
        *       for list of added actions.
        */
    } else {
        /**
        * Handle message history download error. Check 'category' property to find out possible
        * issue because of which request did fail.
        *
        * Request can be resent using: [status retry];
        */
    }
}];
```

## Terms in this document

* **Channel** - A pathway for sending and receiving messages between devices, created automatically when you first use it, that can handle any number of users and messages for different communication needs, like 1-1 text chats, group conversations, and other data streaming.
* **Message** - A unit of data transmitted between clients or between a client and a server in PubNub, containing information such as text, binary data, or structured data formats like JSON. Messages are sent over channels and can be tracked for delivery and read status.
* **PubNub** - PubNub is a real-time messaging platform that provides APIs and SDKs for building scalable applications. It handles the complex infrastructure of real-time communication, including: Message delivery and persistence, Presence detection, Access control, Push notifications, File sharing, Serverless processing with Functions and Events & Actions, Analytics and monitoring with BizOps Workspace, AI-powered insights with Illuminate.
* **Timetoken** - A unique identifier for each message that represents the number of 100-nanosecond intervals since January 1, 1970, for example, 16200000000000000.