# Feature flags в iOS SDK

`Pushwoosh.configure.featureFlags` передаёт вашему iOS-приложению [feature flags](/ru/product/audience-data-and-segmentation/feature-flags/), настроенные для приложения в Панели управления. Pushwoosh вычисляет правила флага на сервере для текущего устройства и пользователя, а SDK возвращает результат: включён ли флаг для этого устройства и какие значения свойств действуют.

<Aside type="tip" title="Бета-функция">
Feature flags находятся в бета-версии и доступны только в тарифах, которые их включают. В остальных тарифах сервер не возвращает флагов, поэтому каждый флаг принимает значение по умолчанию, которое вы передаёте в коде.
</Aside>

## Требования

* Pushwoosh iOS SDK 7.2.10 или новее, интегрированный, как описано в [обзоре iOS SDK](/ru/developer/pushwoosh-sdk/ios-sdk/). Feature flags входят в обязательные модули `PushwooshFramework` и `PushwooshCore`, поэтому добавлять другой модуль не нужно. Они работают в самом iOS-приложении. Расширения приложения, например Notification Service Extension, никогда не запрашивают флаги.
* Хотя бы один флаг, созданный в **Audience** → **Feature Flags**. Вам понадобятся его **Key**, а также ключи и типы его свойств. Используйте ключи точно в том виде, в каком они указаны там, с учётом регистра. Например, `get("New_Checkout")` не найдёт флаг с ключом `new_checkout`.
* Если приложение отправляет запросы SDK через обратный прокси, его URL должен заканчиваться на `json/1.3/`. Иначе SDK не запрашивает флаги, и каждый флаг принимает ваше значение по умолчанию.

## Чтение флага

Всегда передавайте значение по умолчанию. Оно действует, пока у SDK нет флагов для устройства, а также когда флаг не существует, архивирован или тариф не включает feature flags.

```swift
import PushwooshFramework

let flags = Pushwoosh.configure.featureFlags

if let checkout = flags.get("new_checkout"), checkout.isEnabled {
    showNewCheckout(
        buttonColor: checkout.string(forProperty: "button_color", default: "blue") ?? "blue",
        maxItems: Int(checkout.number(forProperty: "max_items", default: 3))
    )
} else {
    showOldCheckout()
}
```

Чтобы только проверить, включён ли флаг, используйте `isEnabled`:

```swift
let isNewCheckout = Pushwoosh.configure.featureFlags.isEnabled("new_checkout", default: false)
```

В Objective-C вызывайте `[PushwooshConfig featureFlags]`.

## Что возвращает флаг

`get(_:)` возвращает объект `PWFeatureFlag` или `nil`, если у SDK ещё нет флагов для устройства или флага нет в ответе. Архивированного флага в ответе нет. `get` читает только сохранённые флаги и никогда не ждёт сети. У объекта есть следующие свойства:

| Свойство | Тип | Описание |
| :---- | :---- | :---- |
| `key` | String | Ключ флага. |
| `isEnabled` | Bool | Включён ли флаг для этого устройства. |
| `ruleId` | String | Правило, определившее результат, например `r_1a2b3c4d`. `default`, если устройству не подошло ни одно правило, и пустая строка, если флаг аварийно отключён (killed). |

Считывайте значения свойств методом, который соответствует типу свойства. Когда флаг выключен, свойства содержат значения по умолчанию, заданные в Панели управления.

| Метод | Возвращает |
| :---- | :---- |
| `string(forProperty:default:)` | Значение свойства типа **String** или ваше значение по умолчанию. |
| `number(forProperty:default:)` | Значение свойства типа **Number** в виде `Double` или ваше значение по умолчанию. |
| `bool(forProperty:default:)` | Значение свойства типа **Boolean** или ваше значение по умолчанию. |
| `jsonObject(forProperty:)` | Значение свойства типа **JSON**, содержащего объект, в виде словаря или `nil`. |
| `jsonArray(forProperty:)` | Значение свойства типа **JSON**, содержащего массив, или `nil`. |

Каждый метод возвращает ваше значение по умолчанию (для JSON-методов — `nil`), если свойство отсутствует или содержит значение другого типа.

`isEnabled(_:default:)` возвращает значение по умолчанию, когда `get(_:)` возвращает `nil`.

<span id="keep-flags-up-to-date" />

## Актуальность флагов

SDK хранит последний ответ на устройстве до 24 часов. Сохранённые флаги действительны только для того User ID и Application Code приложения, для которых они были запрошены. SDK обновляет их:

* при старте сессии, то есть когда приложение становится активным впервые или после не менее 30 минут в фоне;
* каждый час, пока приложение на переднем плане;
* после регистрации устройства для push-уведомлений, если предыдущий запрос был отправлен до регистрации;
* сразу после смены User ID через `setUserId`. Сохранённые флаги предыдущего пользователя удаляются, поэтому новый пользователь их никогда не увидит.

Чтобы реагировать на новые значения, добавьте слушатель, реализующий `PWFeatureFlagsUpdateListener`. Его метод `featureFlagsDidUpdate()` вызывается в главном потоке после обновления, которое изменило какой-либо флаг. SDK хранит слушатели по слабой ссылке. Держите на свой слушатель сильную ссылку, иначе он будет освобождён и никогда не вызовется:

```swift
final class CheckoutViewController: UIViewController, PWFeatureFlagsUpdateListener {
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        Pushwoosh.configure.featureFlags.addUpdateListener(self)
    }

    override func viewWillDisappear(_ animated: Bool) {
        Pushwoosh.configure.featureFlags.removeUpdateListener(self)
        super.viewWillDisappear(animated)
    }

    func featureFlagsDidUpdate() {
        render()
    }
}
```

Чтобы запросить флаги самостоятельно, например после действия, которое может перевести пользователя в другой сегмент, вызовите `refresh(completion:)`. Пока сохранённые флаги действительны, метод отправляет не более одного запроса за 5 минут, а в пределах этого окна отвечает сохранёнными флагами. Completion выполняется в главном потоке:

```swift
Pushwoosh.configure.featureFlags.refresh { error in
    if let error = error as NSError? {
        let status = error.code
        // the stored flags, or your defaults, stay in effect
    }
    render()
}
```

При ошибке у неё домен `PWFeatureFlagsErrorDomain`, а её `code` — HTTP-статус. Код равен `0`, если запрос не получил HTTP-ответа или не был отправлен, и `200`, если сервер ответил, но ответ не удалось использовать.

Чтобы тестировать обновления без 5-минутного ограничения, задайте в `Info.plist` ключу типа Boolean `Pushwoosh_FEATURE_FLAGS_DISABLE_THROTTLE` значение `YES`.

Если Pushwoosh не успевает определить сегменты устройства, он возвращает частичный ответ. SDK не сохраняет его на устройстве, и следующее обновление приносит полный ответ.

Когда флаг аварийно отключают (kill) в Панели управления, устройства при следующем обновлении получают его выключенным со значениями свойств по умолчанию. Когда SDK не может связаться с сервером, он продолжает использовать сохранённые флаги, пока им не исполнится 24 часа, а после этого — ваши значения по умолчанию.

<span id="log-impressions" />

## Запись показов

Показ (impression) сообщает Pushwoosh, что пользователь действительно дошёл до кода, которым управляет флаг, чтобы вы могли сравнить пользователей, получивших функцию, с теми, кто её не получил. Вызывайте `logImpression(_:)` в момент, когда пользователь видит функцию или её старую версию, — и во включённой, и в выключенной ветке, потому что пользователи с выключенным флагом и есть ваша группа сравнения:

```swift
let flags = Pushwoosh.configure.featureFlags
let isOn = flags.isEnabled("new_checkout", default: false)
flags.logImpression("new_checkout")
if isOn {
    showNewCheckout()
} else {
    showOldCheckout()
}
```

SDK отправляет событие `PW_FeatureFlagImpression` со следующими атрибутами:

| Атрибут | Тип | Значение |
| :---- | :---- | :---- |
| `flag_key` | string | Ключ флага. |
| `rule_id` | string | Правило, определившее результат, как в `ruleId`. |
| `enabled` | boolean | Был ли флаг включён. |

Создавать событие заранее не нужно: Pushwoosh добавляет `PW_FeatureFlagImpression` с этими атрибутами в **Audience** → **Events**, когда приходит первый показ. SDK отправляет один показ на флаг, правило и состояние за сессию и ничего не отправляет, если для ключа нет флага. Если отправить не удалось, следующий вызов `logImpression` для этого флага отправит показ повторно.

Показы также позволяют маркетологам построить сегмент пользователей, дошедших до функции: состояние **видел фичу (30 дней)** в [условии по feature flag](/ru/product/audience-data-and-segmentation/feature-flags/#find-users-who-reached-a-feature).

## Справочник методов

У `Pushwoosh.configure.featureFlags` есть следующие методы:

| Метод | Описание |
| :---- | :---- |
| `get(_:)` | Возвращает `PWFeatureFlag` для ключа или `nil`. Читает только сохранённые флаги. |
| `isEnabled(_:default:)` | Возвращает, включён ли флаг, или значение по умолчанию, если флага нет. |
| `refresh(completion:)` | Запрашивает флаги не чаще одного раза за 5 минут, пока сохранённые флаги действительны. Completion получает `nil` или ошибку в `PWFeatureFlagsErrorDomain` и может быть `nil`. |
| `addUpdateListener(_:)` | Вызывает `featureFlagsDidUpdate()` слушателя после каждого обновления, которое изменило флаг. Слушатель хранится по слабой ссылке. |
| `removeUpdateListener(_:)` | Удаляет слушатель, добавленный через `addUpdateListener(_:)`. |
| `logImpression(_:)` | Отправляет событие `PW_FeatureFlagImpression` для текущего правила и состояния флага. |