# Store channel metadata (/en/realtime-media/rtm/build/manage-presence-and-metadata/storage/store-channel-metadata/macos)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

The Signaling storage service enables you to store and share contextual channel data in your app, such as props, announcements, member lists, and relationship chains. When channel metadata is set, updated, or deleted, the SDK triggers a storage event notification. Other users in the channel receive this notification within 100ms and use the information according to your business logic.

    ## Understand the tech [#understand-the-tech-3]

    Use channel metadata to store and share channel level information such as room attributes, group announcements, and auction item price updates. A set of channel metadata for a specific channel facilitates business-level data storage and real-time notifications. Each channel has only one set of channel metadata, but each set may contain multiple metadata items. For relevant restrictions, refer to the [API usage restrictions](../../../reference/limitations). Each metadata item has `key`, `value`, and `revision` properties.

    Channel metadata is stored permanently in the Signaling database. The data persists even after a channel is destroyed. You must explicitly delete it to remove it from the database. This feature impacts your storage billing. Refer to [Pricing](../../../reference/pricing) for details.

    The storage service is available for both message channels and stream channels. Use the `channelType` parameter in the storage event to determine the channel type.

    ## Prerequisites [#prerequisites-3]

    Ensure that you have:

    * Integrated the Signaling SDK in your project.
    * Implemented the framework functionality from the [SDK quickstart](../../../quickstart) page.
    * Enabled storage in [Storage configuration](../../../manage-agora-account#storage-configuration).

    ## Implement channel metadata storage [#implement-channel-metadata-storage-3]

    The section shows you how to implement channel metadata storage in your Signaling app.

    ### Set channel metadata [#set-channel-metadata-3]

    To create a new metadata item for the channel, or to update the `value` of an existing item, call `setChannelMetadata`. This method creates a new item in the channel metadata if the specified `key` does not exist, or overwrites the associated `value` if a metadata item with the specified `key` already exists.

    The following example saves a set of metadata items for a message channel named `channel1`. Signaling adds `timestamp` and `authorUid` information to each metadata item it stores.

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        // Retrieve metadata
        let metadata = AgoraRtmMetadata()!

        // Set metadata items
        let properties = AgoraRtmMetadataItem()
        properties.key = "Quantity"
        properties.value = "20"

        let announcement = AgoraRtmMetadataItem()
        announcement.key = "Announcement"
        announcement.value = "Welcome to our shop!"

        let price = AgoraRtmMetadataItem()
        price.key = "T-shirt"
        price.value = "100"

        // Create an array of metadata items
        let itemArray = [properties, announcement, price]
        metadata.items = itemArray

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.recordTs = true

        rtm.getStorage()?.setChannelMetadata(channelName: "channel1", channelType: .message, data: metadata, options: metadataOpt, lock: nil) { response, errorInfo in
            if errorInfo == nil {
                print("setChannelMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("setChannelMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers an `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        // Initialize a new AgoraRtmMetadata object
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        // Set metadata items
        AgoraRtmMetadataItem* properties = [[AgoraRtmMetadataItem alloc] init];
        properties.key = @"Quantity";
        properties.value = @"20";
        AgoraRtmMetadataItem* announcement = [[AgoraRtmMetadataItem alloc] init];
        announcement.key = @"Announcement";
        announcement.value = @"Welcome to our shop!";
        AgoraRtmMetadataItem* price = [[AgoraRtmMetadataItem alloc] init];
        price.key = @"T-shirt";
        price.value = @"100";

        NSArray *item_array = [NSArray arrayWithObjects:properties, announcement, price];
        metadata.items = item_array;

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;

        [[rtm getStorage] setChannelMetadata:@"channel1" channelType:AgoraRtmChannelTypeMessage data:metadata options:metadata_opt lock:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"setChannelMetadata success!!");
            } else {
                NSLog(@"setChannelMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers an `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    ### Get channel metadata [#get-channel-metadata-3]

    To retrieve all metadata items associated with a specific channel, call `getChannelMetadata` by specifying the channel name and the channel type. Refer to the following example:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        rtm.getStorage()?.getChannelMetadata(channelName: "channel1",, channelType: .message) { response, errorInfo in
            if errorInfo == nil {
                print("getChannelMetadata success!!")
                if let data = response?.data {
                }
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("getChannelMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        [[rtm getStorage] getChannelMetadata:@"channel1" channelType:AgoraRtmChannelTypeMessage completion:^(AgoraRtmGetMetadataResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"getChannelMetadata success!!");
                AgoraRtmMetadata* data = response.data; //get storage data;
            } else {
                NSLog(@"getChannelMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    Signaling SDK returns the following data structure:

    ```js
    {
        majorRevision: 734874892,
        metadata:{
            "Quantity":{
                value:"20",
                revision:734874888,
                updated:1688978391900,
                authorUid:"Tony"
            },
            "Announcement":{
                value:"Welcome to our Shop!",
                revision:734874333,
                updated:1688978391800,
                authorUid:"Tomas"
            },
            "T-shirt":{
                value:"100",
                revision:734874222,
                updated:168897839100,
                authorUid:"Adam"
            }
        }
    }
    ```

    ### Update channel metadata [#update-channel-metadata-3]

    To modify existing metadata items for a specified channel, call `updateChannelMetadata`. If the metadata item does not exist, an error is returned. This method is useful for business use-cases that require permission control on creating new metadata items. For example, consider the following use-cases:

    * In an e-commerce auction, only administrators or product owners are authorized to list new products and set new attributes, while bidders may only modify price attributes.
    * In a gaming environment, only the administrator may define permissions as room properties.

    The following example updates the value of a metadata item:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        // Retrieve metadata
        let metadata = AgoraRtmMetadata()!

        // Set a metadata item
        let price = AgoraRtmMetadataItem()
        price.key = "T-shirt"
        price.value = "299"

        // Create an array of metadata items
        let itemArray = [price]
        metadata.items = itemArray

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.recordTs = true

        // Update channel metadata
        rtm.getStorage()?.updateChannelMetadata(channelName: "channel1", channelType: .message, data: metadata, options: metadataOpt, lock: nil) { response, errorInfo in
            if errorInfo == nil {
                print("updateChannelMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("updateChannelMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers an `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        // Initialize a new AgoraRtmMetadata object
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        // Set metadata items
        AgoraRtmMetadataItem* price = [[AgoraRtmMetadataItem alloc] init];
        price.key = @"T-shirt";
        price.value = @"299";

        NSArray *item_array = [NSArray arrayWithObjects:price];
        metadata.items = item_array;

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;

        [[rtm getStorage] updateChannelMetadata:@"channel1" channelType:AgoraRtmChannelTypeMessage data:metadata options:metadata_opt lock:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"updateChannelMetadata success!!");
            } else {
                NSLog(@"updateChannelMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers an `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    ### Delete channel metadata [#delete-channel-metadata-3]

    To delete metadata items that are no longer required, call `removeChannelMetadata`. Refer to the following sample code:

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let announcement = AgoraRtmMetadataItem()
        announcement.key = "Announcement"

        let itemArray = [announcement]
        metadata.items = itemArray

        rtm.getStorage()?.removeChannelMetadata(channelName: "channel1", channelType: .message, data: metadata, options: nil, lock: nil) { response, errorInfo in
            if errorInfo == nil {
                print("removeChannelMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("removeChannelMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```

        Setting the `value` for a metadata item that is being deleted has no effect.

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers an `didReceiveStorageEvent` notification of event type `update` within 100 ms to inform other channel members.
      </TabsContent>

      <TabsContent value="objc">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];
        AgoraRtmMetadataItem* announcement = [[AgoraRtmMetadataItem alloc] init];
        announcement.key = @"Announcement";
        NSArray *item_array = [NSArray arrayWithObjects:announcement];
        metadata.items = item_array;

        [[rtm getStorage] removeChannelMetadata:@"channel1" channelType:AgoraRtmChannelTypeMessage data:metadata options:nil lock:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"removeChannelMetadata success!!");
            } else {
                NSLog(@"removeChannelMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```

        Setting a `value` for a metadata item that is being deleted has no effect.

        When the call is successful, the SDK returns the `AgoraRtmCommonResponse` data structure. Additionally, Signaling triggers an `didReceiveStorageEvent` notification of event type `AgoraRtmStorageEventTypeUpdate` within 100 ms to inform other channel members.
      </TabsContent>
    </Tabs>

    To delete the entire set of metadata for a channel, do not specify any metadata items when calling `removeChannelMetadata`. Refer to the following sample code:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        let metadata = AgoraRtmMetadata()!

        let announcement = AgoraRtmMetadataItem()
        announcement.key = "Announcement"

        let itemArray = [announcement]
        metadata.items = itemArray

        rtm.getStorage()?.removeChannelMetadata(channelName: "channel1", channelType: .message, data: metadata, options: nil, lock: nil) { response, errorInfo in
            if errorInfo == nil {
                print("removeChannelMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("removeChannelMetadata failed, errorCode: \(errorCode), reason: \(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];;

        [[rtm getStorage] removeChannelMetadata:@"channel1" channelType:AgoraRtmChannelTypeMessage data:metadata options:nil lock:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"removeChannelMetadata success!!");
            } else {
                NSLog(@"removeChannelMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    <CalloutContainer type="info">
      <CalloutDescription>
        Once channel metadata is deleted, it cannot be recovered. To implement data restoration, back up the metadata before deleting it.
      </CalloutDescription>
    </CalloutContainer>

    ## Receive storage event notifications [#receive-storage-event-notifications-3]

    A storage event notification returns the [AgoraRtmStorageEvent](/en/api-reference/api-ref/signaling#configstorageeventpropsag_platform) data structure, which includes the [AgoraRtmStorageEventType](/en/api-reference/api-ref/signaling#enumvstorageeventtypepropsag_platform) parameter.

    To receive storage event notifications, implement an event listener. See [event listeners](/en/api-reference/api-ref/signaling#event-listeners) for details. In addition, add `metadata` to the `features` parameter under `options` when subscribing to or joining a channel.

    #### Event notification mode [#event-notification-mode-3]

    Currently, Signaling only supports the full data update mode. This means that when user or channel metadata is updated, the `data` field in the event notification contains all the attribute data of the user or the channel.

    ## Additional storage features [#additional-storage-features-3]

    To help resolve issues arising from concurrent updates to storage, Signaling offers version control and locking features.

    ### Version control [#version-control-3]

    The Compare and Set (CAS) version control feature in channel metadata storage provides two independent version control parameters. Set one or more of these values according to the needs of your business use-case:

    * `majorRevision` parameter in the `setMajorRevision` method: Enable version number verification of the entire set of channel metadata.

    * `revision` parameter of a `AgoraRtmMetadataItem`: Enable version number verification of a single metadata item.

    When setting channel metadata, or a single channel metadata item, use the revision attribute to enable or disable version control as follows:

    * To disable CAS verification, use the default value of `-1` for the `revision` parameter. If the channel metadata or channel metadata item already exists, the value is overwritten. If it does not exist, the SDK creates a metadata item and updates the value.

    * To enable CAS verification, set the `majorRevision` or the `revision` parameter to a positive integer. If the channel metadata or channel metadata item already exists, the SDK updates the corresponding value after successfully verifying the revision number. If the specified revision number does not match the latest revision number in the database, the SDK returns an error code.

    The following sample shows how to use `majorRevision` and `revision` to update channel metadata and a metadata item:

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        let metadata = AgoraRtmMetadata()

        let properties = AgoraRtmMetadataItem()
        properties.key = "Quantity"
        properties.value = "30"
        properties.revision = 734874888

        let announcement = AgoraRtmMetadataItem()
        announcement.key = "Announcement"
        announcement.value = "Welcome to our shop!"

        let price = AgoraRtmMetadataItem()
        price.key = "T-shirt"
        price.value = "101"
        price.revision = 734874222

        let itemArray = [properties, announcement, price]
        metadata.items = itemArray

        metadata.majorRevision = 734874892

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.records = true

        rtm.getStorage()?.updateChannelMetadata(channelName: "channel1", channelType: .message, data: metadata, options: metadataOpt, lock: nil) { response, errorInfo in
            if errorInfo == nil {
                print("updateChannelMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("updateChannelMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* properties = [[AgoraRtmMetadataItem alloc] init];
        properties.key = @"Quantity";
        properties.value = @"30";
        properties.revision = 734874888;
        AgoraRtmMetadataItem* announcement = [[AgoraRtmMetadataItem alloc] init];
        announcement.key = @"Announcement";
        announcement.value = @"Welcome to our shop!";
        AgoraRtmMetadataItem* price = [[AgoraRtmMetadataItem alloc] init];
        price.key = @"T-shirt";
        price.value = @"101";
        price.revision = 734874222;
        NSArray *item_array = [NSArray arrayWithObjects:properties, announcement, price];
        metadata.items = item_array;
        metadata.majorRevision = 734874892; // set marjor revision

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;

        [[rtm getStorage] updateChannelMetadata:@"channel1" channelType:AgoraRtmChannelTypeMessage data:metadata options:metadata_opt lock:nil completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"updateChannelMetadata success!!");
            } else {
                NSLog(@"updateChannelMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    In the above example, CAS verification for channel metadata and metadata items is enabled by setting `majorRevision` and `revision` parameters to positive integers. Upon receiving the update call request, Signaling first verifies the provided major revision number against the latest value in the database. If there's a mismatch, it returns an error. If the values match, Signaling verifies the `revision` number for each metadata item using a similar logic.

    <CalloutContainer type="info">
      <CalloutDescription>
        When using version control, monitor `didReceiveStorageEvent` notifications to retrieve updated values for `majorRevision` and `revision` to ensure that the latest revision values are used for subsequent operations.
      </CalloutDescription>
    </CalloutContainer>

    Following are some sample use-cases where version control is useful:

    * In a bidding use-case, if multiple users bid on a product at the same time, the first bidder succeeds, while others receive an error. Users obtain the latest price information to update their bids.

    * In a red envelope grabbing use-case, the red envelope may only be grabbed once. The first user succeeds, while the rest receive an error.

    ### Locks [#locks-3]

    Locks enable users to gain exclusive access to critical resources, resolving contention issues with shared resources. For instance, consider a use-case where only one administrator is allowed in a channel at a time, and only the administrator can manage channel metadata by setting, deleting, and modifying it.

    Compared to CAS, which controls the version of channel metadata, locks offer a higher level of control. They determine whether a user has the authority to call the `setChannelMetadata`, `updateChannelMetadata`, and `removeChannelMetadata` interfaces. Without acquiring the lock, a user cannot perform operations on channel metadata.

    The following code demonstrates using a lock to update channel metadata. The user calling `updateChannelMetadata` must acquire the lock first for the call to succeed.

    <CodeBlockTabs defaultValue="Swift">
      <CodeBlockTabsList>
        <CodeBlockTabsTrigger value="Swift">
          Swift
        </CodeBlockTabsTrigger>

        <CodeBlockTabsTrigger value="Objective-C">
          Objective-C
        </CodeBlockTabsTrigger>
      </CodeBlockTabsList>

      <CodeBlockTab value="Swift">
        ```swift
        let metadata = AgoraRtmMetadata()

        let properties = AgoraRtmMetadataItem()
        properties.key = "Quantity"
        properties.value = "30"
        properties.revision = 734874888

        let announcement = AgoraRtmMetadataItem()
        announcement.key = "Announcement"
        announcement.value = "Welcome to our shop!"

        let price = AgoraRtmMetadataItem()
        price.key = "T-shirt"
        price.value = "101"
        price.revision = 734874222

        let itemArray = [properties, announcement, price]
        metadata.items = itemArray

        metadata.majorRevision = 734874892

        let metadataOpt = AgoraRtmMetadataOptions()
        metadataOpt.recordUserId = true
        metadataOpt.recordTs = true

        rtm.getStorage()?.updateChannelMetadata(channelName: "channel1", channelType: .message, data: metadata, options: metadataOpt, lock: "manage") { response, errorInfo in
            if errorInfo == nil {
                print("updateChannelMetadata success!!")
            } else {
                if let errorCode = errorInfo?.errorCode, let reason = errorInfo?.reason {
                    print("updateChannelMetadata failed, errorCode: \\(errorCode), reason: \\(reason)")
                }
            }
        }
        ```
      </CodeBlockTab>

      <CodeBlockTab value="Objective-C">
        ```objc
        AgoraRtmMetadata* metadata = [[AgoraRtmMetadata alloc] init];

        AgoraRtmMetadataItem* properties = [[AgoraRtmMetadataItem alloc] init];
        properties.key = @"Quantity";
        properties.value = @"40";
        AgoraRtmMetadataItem* announcement = [[AgoraRtmMetadataItem alloc] init];
        announcement.key = @"Announcement";
        announcement.value = @"Welcome to our shop!";
        AgoraRtmMetadataItem* price = [[AgoraRtmMetadataItem alloc] init];
        price.key = @"T-shirt";
        price.value = @"300";

        NSArray *item_array = [NSArray arrayWithObjects:properties, announcement, price];
        metadata.items = item_array;

        AgoraRtmMetadataOptions* metadata_opt = [[AgoraRtmMetadataOptions alloc] init];
        metadata_opt.recordUserId = true;
        metadata_opt.recordTs = true;

        [[rtm getStorage] updateChannelMetadata:@"channel1" channelType:AgoraRtmChannelTypeMessage data:metadata options:metadata_opt lock:@"manage" completion:^(AgoraRtmCommonResponse * _Nullable response, AgoraRtmErrorInfo * _Nullable errorInfo) {
            if (errorInfo == nil) {
                NSLog(@"updateChannelMetadata success!!");
            } else {
                NSLog(@"updateChannelMetadata failed, errorCode %d, reason %@", errorInfo.errorCode, errorInfo.reason);
            }
        }];
        ```
      </CodeBlockTab>
    </CodeBlockTabs>

    For more information on setting, acquiring, releasing, revoking, and removing locks, see [Locks](/en/api-reference/api-ref/signaling#lock).

    ## Reference [#reference-3]

    This section contains content that completes the information on this page, or points you to documentation that explains other aspects to this product.

    ### API reference [#api-reference-3]

    <Tabs defaultValue="swift" groupId="language">
      <TabsList>
        <TabsTrigger value="swift">
          Swift
        </TabsTrigger>

        <TabsTrigger value="objc">
          Objective-C
        </TabsTrigger>
      </TabsList>

      <TabsContent value="swift">
        * [`setChannelMetaData`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagesetchannelpropsag_platform)
        * [`getChannelMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagegetchannelpropsag_platform)
        * [`removeChannelMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageremovechannelpropsag_platform)
        * [`updateChannelMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storageupdatechannelpropsag_platform)
        * [`AgoraRtmMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#storagemetadatapropsag_platform)
        * [Event listeners](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#event-listeners)
        * [Lock](/en/api-reference/api-ref/signaling?platform=ios\&tab=swift#lock)
      </TabsContent>

      <TabsContent value="objc">
        * [`setChannelMetaData`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagesetchannelpropsag_platform)
        * [`getChannelMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagegetchannelpropsag_platform)
        * [`removeChannelMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageremovechannelpropsag_platform)
        * [`updateChannelMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storageupdatechannelpropsag_platform)
        * [`AgoraRtmMetadata`](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#storagemetadatapropsag_platform)
        * [Event listeners](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#event-listeners)
        * [Lock](/en/api-reference/api-ref/signaling?platform=ios\&tab=objc#lock)
      </TabsContent>
    </Tabs>

    
  
      
  
      
  
      
  
      
  
