---
source_url: https://www.pubnub.com/docs/sdks/cocoa-objective-c/api-reference/storage-and-playback
title: Cocoa Message Persistence API Reference for Real-time Apps
updated_at: 2026-06-03T11:42:05.465Z
---

> 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


# Cocoa Message Persistence API Reference for Real-time Apps

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)

## 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-).
:::

This function fetches historical messages of a channel.

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

* 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 timeline by providing both a `start` and `end` timetoken.
* Limit the number of messages using the `limit` parameter.

:::tip Start & End parameter usage clarity
If you specify only `start` (without `end`), you receive messages older than and up to that `start` timetoken. If you specify only `end` (without `start`), you receive messages that match that `end` timetoken and newer. If you specify both `start` and `end`, you receive messages between those timetokens (inclusive of `end`). You still receive up to 100 messages even if more match. Make iterative calls to history, adjusting `start`, to page through the full result set.
:::

### Method(s)

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

#### History with block

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

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| channel | NSString | Yes |  | `Channel` name to retrieve the History information. |
| block | PNHistoryCompletionBlock | Yes |  | History completion block that delivers `result` and `status`. |

#### History with metadata and block

```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 to include metadata in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block. |

#### History with message reactions and 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 to include message actions in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block. |

#### History with metadata, message reactions, and 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 to include metadata in the response. |
| `shouldIncludeMessageActions` *Type: BOOL | Whether to include message actions in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block. |

#### History with dates and 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 | Timetoken of the oldest event to include (exclusive). |
| `endDate`Type: NSNumber | Timetoken of the latest event to include (inclusive). |
| `block` *Type: PNHistoryCompletionBlock | History completion block that delivers `result` and `status`. |

#### History with dates, metadata, and block

```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 of the oldest event to include (exclusive). |
| `endDate`Type: NSNumber | Timetoken of the latest event to include (inclusive). |
| `shouldIncludeMetadata` *Type: BOOL | Whether to include metadata in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block. |

#### History with dates, message reactions, and 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 of the oldest event to include (exclusive). |
| `endDate`Type: NSNumber | Timetoken of the latest event to include (inclusive). |
| `shouldIncludeMessageActions` *Type: BOOL | Whether to include message actions in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block. |

#### History with dates, metadata, message reactions, and block

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

| Parameter | Description |
| --- | --- |
| `channel` *Type: NSString | `Channel` name to retrieve the History information. |
| `startDate`Type: NSNumber | Timetoken of the oldest event to include (exclusive). |
| `endDate`Type: NSNumber | Timetoken of the latest event to include (inclusive). |
| `shouldIncludeMetadata` *Type: BOOL | Whether to include metadata in the response. |
| `shouldIncludeMessageActions` *Type: BOOL | Whether to include message actions in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block. |

#### History with dates, limits, and 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 | Timetoken of the oldest event to include (exclusive). |
| `endDate`Type: NSNumber | Timetoken of the latest event to include (inclusive). |
| `limit` *Type: NSUInteger | Maximum number of events to return (no more than 100). |
| `block` *Type: PNHistoryCompletionBlock | History completion block that delivers `result` and `status`. |

#### History with dates, timetoken, and 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 | Timetoken delimiting the start of the time slice (exclusive). |
| `endDate`Type: NSNumber | Timetoken delimiting the end of the time slice (inclusive). |
| `shouldIncludeTimeToken` *Type: Bool | Whether to include message timestamps (timetokens) in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block that delivers `result` and `status`. |

#### History with dates, limits, timetoken, and block

```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 | Timetoken delimiting the start of the time slice (exclusive). |
| `endDate`Type: NSNumber | Timetoken delimiting the end of the time slice (inclusive). |
| `limit` *Type: NSUInteger | Number of historical messages to return (maximum 100). |
| `shouldIncludeTimeToken` *Type: Bool | Whether to include message timestamps (timetokens) in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block that delivers `result` and `status`. |

#### History with dates, limit, resersed order, and block

```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 | Timetoken of the oldest event to include (exclusive). |
| `endDate`Type: NSNumber | Timetoken of the latest event to include (inclusive). |
| `limit` *Type: NSUInteger | Number of historical messages to return (maximum 100). |
| `shouldReverseOrder` *Type: Bool | Whether to traverse in reverse (oldest to newest). If both `startDate` and `endDate` are provided, reverse is ignored and messages are returned newest first. |
| `block` *Type: PNHistoryCompletionBlock | History completion block that delivers `result` and `status`. |

#### History with dates, limits, timetoken, and block, in reverse order

```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 | Timetoken delimiting the start of the time slice (exclusive). |
| `endDate`Type: NSNumber | Timetoken delimiting the end of the time slice (inclusive). |
| `limit` *Type: NSUInteger | Number of historical messages to return (maximum 100). |
| `shouldReverseOrder` *Type: Bool | Whether to traverse in reverse (oldest to newest). If both `startDate` and `endDate` are provided, reverse is ignored and messages are returned newest first. |
| `shouldIncludeTimeToken` *Type: Bool | Whether to include message timestamps (timetokens) in the response. |
| `block` *Type: PNHistoryCompletionBlock | History completion block that delivers `result` and `status`. |

:::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];
        */
    }
}];
```

## History (builder pattern)

This function fetches historical messages of a channel.

This method uses the builder pattern; you can omit any optional arguments.

### Method(s)

To get channel history using the Builder pattern, use the following method(s) in the Cocoa SDK:

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

| Parameter | Description |
| --- | --- |
| `channels` *Type: NSString | List of `channels` for which history should be returned. |
| `start`Type: NSNumber | Timetoken for oldest event starting from which next should be returned events. Value will be converted to required precision internally. |
| `end`Type: NSNumber | Timetoken for latest event till which events should be pulled out. Value will be converted to required precision internally. |
| `limit`Type: NSUInteger | Maximum number of messages which should be returned for each channel. Default and maximum value is 100 for a single channel, 25 for multiple channels, and 25 when `includeMessageActions` is `YES`. |
| `reverse`Type: BOOL | Setting to `YES` traverses the time line in reverse, starting with the oldest message first. |
| `includeMetadata`Type: BOOL | Whether event metadata should be included in response or not. |
| `shouldIncludeMessageType`Type: BOOL | Pass `YES` to receive the message type with each history message. Default is `YES`. |
| `shouldIncludeUUID`Type: BOOL | Pass `YES` to receive the publisher uuid with each history message. Default is `YES`. |
| `includeMessageActions`Type: BOOL | Whether event actions should be included in response or not. When `YES`, throws an exception if you call the method with more than one `channel`. |
| `includeTimeToken`Type: BOOL | Whether event dates (timetokens) should be included in response or not. |
| `block` *Type: PNHistoryCompletionBlock | History pull 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
self.client.history()
    .channels(@[@"my_channel"])
    .limit(15)
    .performWithCompletion(^(PNHistoryResult *result, PNErrorStatus *status) {

    if (status == nil) {

        /**
         Handle downloaded history using:
         result.data.channels - dictionary with channels' history. Each key is channel name and value is
                                list of fetched 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

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
This method requires that Message Persistence is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/).
:::

Removes the messages from the history of a specific channel.

:::note Delete-From-History
There is a setting to accept delete from history requests for a key, which you must enable by checking the Enable `Delete-From-History` checkbox in the key settings for your key in the Admin Portal.
Requires Initialization with secret key.
:::

### Method(s)

To `Delete Messages from History` you can use the following method(s) in the Cocoa 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
This method requires that Message Persistence is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/).
:::

Removes the messages from the history of a specific channel.

:::note Delete-From-History
There is a setting to accept delete from history requests for a key, which you must enable by checking the Enable `Delete-From-History` checkbox in the key settings for your key in the Admin Portal.
Requires Initialization with secret key.
:::

### Method(s)

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

```objectivec
- deleteMessage()
    .channel(NSString *)
    .start(NSNumber *)
    .end(NSNumber *)
    .performWithCompletion(nullable 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
This method requires that Message Persistence is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/).
:::

Returns the number of messages published on one or more channels since a given time. The `count` returned is the number of messages in history with a `timetoken` value `greater than or equal to` than the passed value in the `timetokens`parameter.

:::note Unlimited message retention
For keys with unlimited message retention enabled, this method considers only messages published in the last 30 days.
:::

### Method(s)

You can use the following method(s) in the Cocoa 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 count
`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]
            */
        }
    });
```

## Terms in this document

* **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.
