# Attributes

In addition of setting a custom user ID, an email address or overriding the language/region, you can assign **attributes** to your users, allowing you to improve your orchestrations targeting.

![Profile attributes](/files/WsxlF6q9WdIx4xBa8mvQ)

> **Important:**
>
> **User IDs** must be managed using our [custom user ID](/developer/sdk/ios/profile-data/custom-user-id.md) implementation.
>
> **Email address** must be managed using our [email subscription](/developer/sdk/ios/profile-data/email-subscription.md) implementation.
>
> **Region/language** data must be managed using our [custom region/language](/developer/sdk/ios/profile-data/custom-locale.md) implementation.
>
> Never use an existing tagging plan.
>
> Newly tracked attributes are hidden by default. You will need to manually display them from the dashboard data page.

### Managing attributes

Before we get started on how to implement attributes, here are some rules you should know.

**Naming**

Attribute names are **strings**. They should be made of letters, numbers or underscores *(\[a-z0-9\_])* and can't be longer than 30 characters *(e.g. has\_premium)*.

**Values**

Values must be any of the following types, or their native Swift equivalent:

**NSString**\
Must not be longer than 300 characters and cannot be empty. For better results, you should make them upper/lowercase and trim the whitespaces.

**NSNumber**

Anything bigger than a `long long` or a `double` will be rejected.

Unsigned values will be rejected.

Booleans are supported, but should be initialized with `[NSNumber numberWithBool:<your value>]` or `@YES/@NO`.

**NSDate**\
Since timezones are not supported, this will typically represent UTC dates.

> Using any unsupported type as a value *(NSNull, NSObject, NSArray, NSDictionary for example)* will **NOT** work. Be careful, as it may cause `[editor save]` to fail.

**NSURL**\
Must not be longer than 2048 characters and must follow the format `scheme://[authority][path][?query][#fragment]`.

**NSArray\<NSString>**\
Must not be longer than 25 items, only values of type NSString and must respect the string attribute limitations.

**Setting an attribute**

The custom attribute API works using an editor. You need to get an instance of the editor, enqueue your changes and then call `save`.\
Changes will NOT be sent until you call this method, please make sure you call it!

There is one `setAttribute/set(attribute:forKey:)` method per attribute type. Use Xcode's autocompletion or see `BatchProfileEditor.h` for all available variants. If you're using Swift, the appropriate method will automatically be used according to the value's type.

Those methods throw an error if key/value failed validation according to the rules expressed higher up in this documentation. Note that no error does not mean the value has already been sent to the server.

{% tabs %}
{% tab title="Swift" %}

```swift
// Get an editor instance.
// You need to save this in a local variable until you call save
// Editor instances don't share changes, and calling save on an empty editor will do nothing
let editor = BatchProfile.editor()

// Set an attribute. try? allows a potential error to be silently ignored
// This example is a valid key/attribute pair, and will not throw an error.
try? editor.set(attribute: 26, forKey:"age")

// Set an array attribute
try? editor.set(attribute: ["add_to_cart", "has_bought"], forKey: "actions")

// Add a string to an array attribute 
try? editor.addToStringArray(item: "has_bought", forKey: "actions")


// Set an attribute with error handling.
do {
    // Invalid attribute name, $ is a forbidden character
    try editor.set(attribute: "patricia", forKey: "fir$t_name")
} catch {
    // Handle the error here.
    // Error is of type BatchProfileError if you want to specifically
    // handle it.
}

// Don't forget to save the changes
editor.save() 

// Alternatively you can use `editWithBlock` and make all your changes in a closure.
BatchProfile.editor { editor in
    try? editor.set(attribute: "patricia", forKey: "firt_name")
     // No need to save here since Batch will automatically save your change after executing the block.
}
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
// Get an editor instance.
// You need to save this in a local variable until you call save
// Editor instances don't share changes, and calling save on an empty editor will do nothing
BatchProfileEditor *editor = [BatchProfile editor];

// Set an attribute, silently ignoring a potential error
// This example is a valid key/attribute pair, and will not throw an error.
[editor setIntegerAttribute:@26 forKey:@"age" error:nil];

// Set an array attribute
[editor setStringArrayAttribute: @[@"added_to_cart", @"has_bought"] forKey:@"actions" error:nil];

// Add a string to an array attribute 
[editor addItemToStringArrayAttribute:@"actions" forKey:@"has_bought" error:nil]

// Set an attribute with error handling.
NSError *err;
if (![editor setStringAttribute:@"patricia" forKey:@"fir$t_name" error:&err]) { ; // Invalid attribute name, $ is a forbidden character
    // Handle the error here.
}
// Don't forget to save the changes
[editor save]; 

// Alternatively you can use `editWithBlock` and make all your changes in a closure.
[BatchProfile editWithBlock:^(BatchProfileEditor * _Nonnull editor) {
    [editor setStringAttribute:@"patricia" forKey:@"firt_name" error:nil];
    // No need to save here since Batch will automatically save your change after executing the block.
}];
```

{% endtab %}
{% endtabs %}

You might be tempted to write helpers or loops that open and save many transactions in a row, with each transaction only doing one operation.\
Doing so prevents Batch from optimizing disk usage and network roundtrips, which impact your user's data plan and battery life.\
Please try to batch as many operations as you can in a single transaction.

> Please test your implementation using our [debug tool](https://doc.batch.com/getting-started/features/mobile-engagement-platform/settings/app-settings#debug-tools) and [profile view](https://doc.batch.com/getting-started/features/customer-engagement-platform/profiles/overview) before releasing your app on the store. Make sure you're unwrapping your optionals!

**Removing attributes**

Use `removeAttributeForKey` to remove an attribute of any type.

{% tabs %}
{% tab title="Swift" %}

```swift
// Get an editor instance.
// You need to save this in a local variable until you call save
// Editor instances don't share changes, and calling save on an empty editor will do nothing
let editor = BatchProfile.editor()

// Remove an attribute
editor.removeAttribute(forKey: "age") 

// Remove a string from an array
try? editor.removeFromStringArray(item: "has_bought", forKey: "actions")

// Don't forget to save the changes
editor.save() 
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
// Get an editor instance.
// You need to save this in a local variable until you call save
// Editor instances don't share changes, and calling save on an empty editor will do nothing
BatchProfileEditor *editor = [BatchProfile editor];

// Remove an attribute
[editor removeAttributeForKey:@"age"]; 

// Remove a string from an array
[editor removeItemFromStringArrayAttribute:@"has_bought" forKey:@"actions" error:nil];

// Don't forget to save the changes
[editor save];
```

{% endtab %}
{% endtabs %}

### Reading attributes and tag collections

Since Batch SDK v2, updating the user's data also update the profile's data to be accessible from your project scope. This mean the following APIs only read local data related to your installation and NOT to your profile.

You may also have noticed that APIs to set Tags or Tag Collections have been removed and replaced by array attributes. These methods are backward-compatible and array attributes are converted into tag collections to not break your implementation.

#### Reading attributes

{% tabs %}
{% tab title="Swift" %}

```swift
BatchUser.fetchAttributes { attributes in
    // Attributes are retrieved in the form of a dictionary
    // Values are encapsulated in an instance of BatchUserAttribute
    let attribute: BatchUserAttribute = attributes["age"]
            
    // BatchUserAttribute holds a reference to the value of the attribute
    let rawValue: Any = attribute.value // Raw value is not typed
    print(rawValue) // Prints "NSNumber(26)"
            
    // The type of the value is specified via a BatchUserAttributeType enumeration
    print(attribute.type) // Prints "BatchUserAttributeTypeLongLong"
            
    // To obtain a typed result you can use one of the four helper methods
    attribute.numberValue() // Will return "26" here
    attribute.dateValue()   // Will return nil here
    attribute.stringValue() // Will return nil here
    attribute.urlValue()    // Will return nil here
}
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[BatchUser fetchAttributes:^(NSDictionary<NSString *,BatchUserAttribute *> * _Nullable attributes) {
    // Attributes are retrieved in the form of a dictionary
    // Values are encapsulated in an instance of BatchUserAttribute
    BatchUserAttribute *attribute = attributes[@"age"];

    // BatchUserAttribute holds a reference to the value of the attribute
    id rawValue = attribute.value; // Raw value is not typed
    NSLog(rawValue); // Prints "NSNumber(26)"
        
    // The type of the value is specified via a BatchUserAttributeType enumeration
    NSLog(attribute.type); // Prints "BatchUserAttributeTypeLongLong"
    
    // To obtain a typed result you can use one of the three helper methods
    [attribute numberValue];    // Will return "26" here
    [attribute dateValue];      // Will return nil here
    [attribute stringValue];    // Will return nil here
}];
```

{% endtab %}
{% endtabs %}

#### Reading tag collections

{% tabs %}
{% tab title="Swift" %}

```swift
BatchUser.fetchTags { tagCollections in
    // Tags are also retrieved in the form of a dictionary
    // Keys are names of collections, values are sets of tags
    let tagCollection: Set<String> = tagCollections["actions"]
    print(tagCollection) // Prints "["has_bought"]"
}
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[BatchUser fetchTags:^(NSDictionary<NSString *,NSSet<NSString *> *> * _Nullable tagCollections) {
    // Tags are also retrieved in the form of a dictionary
    // Keys are names of collections, values are sets of tags
    NSSet<NSString *> *tagCollection = tagCollections[@"actions"];
    NSLog(tagCollection) // Prints "["has_bought"]"
}];
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Note: Since tags are limited in size and are case sensitive, reading them back might produce different results than what had been saved.
{% endhint %}

### Clearing installation data

Clearing the installation data will delete all tags and attributes set on an installation and their local cache returned by `fetchAttributes` and `fetchTagCollections`. It can be done as following:

{% tabs %}
{% tab title="Swift" %}

```swift
BatchUser.clearInstallationData();
```

{% endtab %}

{% tab title="Objective-C" %}

```objectivec
[BatchUser clearInstallationData];
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
**Note:** This will **NOT** affect the profile related data.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.batch.com/developer/sdk/ios/profile-data/attributes.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
