Перейти к основному содержимому

SigmaSDK - iOS

Sigma - это платформа для экспериментов, которая позволяет вам быстро оценивать влияние новых функций и предлагать продукты, которые нравятся вашим клиентам.

Требования

  • iOS 11+

Установка SDK

Cocoapods

Если ваш проект использует Cocoapods, добавьте зависимость SigmaSDK в Podfile:

use_frameworks!
target 'MyApp' do
pod 'SigmaSDK', '~> X.Y.Z'
# ...
end

Swift Package Manager

Если ваш проект использует Swift Package Manager, добавьте пакет SigmaSDK через Xcode:

File > Add Package > Search or Enter Package URL > https://github.com/expfdev/sigma_ios

Или вручную в файле Package.swift:

dependencies: [
.package(url: "https://github.com/expfdev/sigma_ios"),
],
targets: [
.target(name: "MyApp", dependencies: ["SigmaSDK"])
],

Работа с SDK

Инициализация SDK

Для работы с SDK необходимо создать объект типа SigmaUser. Он хранит информацию о пользователе, такую как его уникальный идентификатор.

Пример создания объекта SigmaUser:

import SigmaSDK

let user = SigmaUser.Builder()
.setUserId("123")
.setCustomProperty(true, key: "isAuthorized")
.setEmail("user123@expf.ru")
.build()

SigmaUser.Builder обладает следующими методами:

  • func setUserId(_ id: String) -> SigmaUser.Builder - назначение ID анонимного пользователя. Используется для раздачи экспериментов и фича-флагов. (если не назначен, то не будет работать сплит экспериментов по userId).
  • func setProfileId(_ id: String) -> SigmaUser.Builder - назначение ID авторизованного пользователя (например, в личном кабинете). (если не назначен, то не будет работать сплит экспериментов по profileId).
  • func setDeviceId(_ id: String) -> SigmaUser.Builder - переопределение Device ID, определенного SDK.
  • func setEmail(_ email: String) -> SigmaUser.Builder - назначение параметра пользователя с названием email.
  • func setAppVersion(_ version: String) -> SigmaUser.Builder - переопределение версии приложения, определенной SDK.
  • func setOsName(_ name: String) -> SigmaUser.Builder - переопределение названия операционной системы, определенной SDK.
  • func setOsVersion(_ version: String) -> SigmaUser.Builder - переопределение версии операционной системы, определенной SDK.
  • func setGeoCode(_ code: String) -> SigmaUser.Builder - переопределение гео кода, определенного SDK.
  • func setGeoCountry(_ country: String) -> SigmaUser.Builder - переопределение страны, определенной SDK.
  • func setGeoState(_ state: String) -> SigmaUser.Builder - переопределение региона, определенного SDK.
  • func setGeoCity(_ code: String) -> SigmaUser.Builder - переопределение города, определенного SDK.
  • func setCustomProperty<Value: CustomStringConvertible>(_ value: Value, key: String) -> SigmaUser.Builder - назначение custom-параметра пользователя (все названия таких параметров имеют префикс custom.).

После создания объекта SigmaUser, необходимо вызывать метод инициализации SDK:

import SigmaSDK

do {
let client = try Sigma.initializeClient(projectToken: token, initialUser: user)
} catch let error {
// Handle error
}

Метод initializeClient обладает следующими параметрами:

  • projectToken: String - токен проекта (указан в панели управления).
  • initialUser: SigmaUser? - Объект SigmaUser. Необязательный параметр. При отсутствии, SDK автоматически создаст объект SigmaUser, сгенерировав ему случайный идентификатор.
  • apiURL: String - URL для Sigma API. Необязательный параметр. По умолчанию равен "https://api.expf.ru/api/v1/".
  • cacheTTL: UInt - частота (в секундах) запрашивания конфигурации из сети. Необязательный параметр. По умолчанию равен 10 секундам.
  • retryCount: UInt - количество повторных попыток запрашивания конфигурации при неудачном запросе. Необязательный параметр. По умолчанию равен 3.
  • tag: String - тег клиента Sigma. Необязательный параметр. По умолчанию равен "default". Необходим для создания нескольких экземпляров SigmaClient.

Для получения клиента SigmaClient используется метод Sigma.getClient(tag: String). Для завершения работы над клиентом используется метод Sigma.removeClient(tag: String)

При инициализации уже инициализированного клиента (с уже существующим тегом) будет выброшена ошибка SigmaError.initializationOfExistingClient.

Обновление информации о пользователе

Доступ к SigmaClient позволяет обновить параметры объекта SigmaUser, принадлежащего клиенту. SigmaClient обладает следующими методами:

  • func setUserProperties(builder: (SigmaUser.Builder) -> SigmaUser.Builder) - переназначение всех свойств пользователя. Ранее объявленные свойства пользователя будут удалены.
  • func editUserProperties(builder: (SigmaUser.Builder) -> SigmaUser.Builder) - обновление свойств пользователя. Ранее объявленные свойства не будут удалены, но их значения могут быть перезаписаны с помощью блока builder.
  • func clearUserProperties() - удаление всех свойств пользователя. Эквивалентно созданию нового пользователя без параметров.

Получение значений Feature Flag

Для получения значений Feature Flag используются следующие методы SigmaClient:

func checkFlag<T: SigmaPropertyType>(flagName: String, onSuccess: SigmaSuccessCallback<T?>?, onError: SigmaErrorCallback?)
func checkFlag<T: SigmaPropertyType>(flagName: String) async throws -> T?

Значение FeatureFlag может быть типа Bool, Int, Double, String или [String: Any].

Пример получения значений Feature Flag:

import SigmaSDK

guard let client = Sigma.getClient() else { return }

// Callback версия
client.checkFlag(
flagName: "my_first_flag",
onSuccess: { (value: Bool?) in
// Обработка значения Feature Flag
},
onError: { error in
// Обработка ошибки
}
)

// Async-await версия
do {
let firstFlag: Bool? = try await client.checkFlag(flagName: "my_first_flag")
// Обработка значения Feature Flag
} catch let error {
// Обработка ошибки
}

Несмотря на то, что методы получения Feature Flag выбрасывают ошибки, даже при отсутствии ошибок значение флага может быть nil. Эта ситуация может возникнуть, например, в случае, если флаг привязан к эксперименту, в который пользователь не попал. Эта ситуация не ошибочная: ожидаемо, что для данного пользователя значение Feature Flag недоступно, поэтому SDK возвращает nil.

Для получения полного списка Feature Flag, которые есть у пользователя, используются следующие методы SigmaClient:

func getUserFeatureFlagsDetails(onSuccess: SigmaSuccessCallback<[String: SigmaPropertyType]>?, onError: SigmaErrorCallback?)
func getUserFeatureFlagsDetails() async throws -> [String: SigmaPropertyType]

Методы возвращают словарь, где ключом является название Feature Flag, а значением - значение Feature Flag. Значение FeatureFlag может быть типа Bool, Int, Double, String или [String: Any].

Пример получения полного списка Feature Flag:

import SigmaSDK

guard let client = Sigma.getClient() else { return }

// Callback версия
client.getUserFeatureFlagsDetails(
onSuccess: { dictionary in
if let dictionaryFlag = dictionary["my_feature_flag"] as? [String: Any] {
// Обработка значения Feature Flag типа [String: Any]
} else {
// Обработка значения Feature Flag типа Bool, Int, Double или String
}
},
onError: { error in
// Обработка ошибки
}
)

// Async-await версия
do {
let dictionary = try await client.getUserFeatureFlagsDetails()
if let dictionaryFlag = dictionary["my_feature_flag"] as? [String: Any] {
// Обработка значения Feature Flag типа [String: Any]
} else {
// Обработка значения Feature Flag типа Bool, Int, Double или String
}
} catch let error {
// Обработка ошибки
}

Получение экспериментов

Для получения всех экспериментов, в которые попал пользователь, используются следующие методы SigmaClient:

func getAllUserExperiments(onSuccess: SigmaSuccessCallback<String?>?, onError: SigmaErrorCallback?)
func getAllUserExperiments() async throws -> String?

Для получения эксперимента по названию, используются следующие методы SigmaClient:

func getExperiment(name: String, onSuccess: SigmaSuccessCallback<SigmaExperiment?>?, onError: SigmaErrorCallback?)
func getExperiment(name: String) async throws -> SigmaExperiment?

Все вышеописанные методы возвращают только те эксперименты, в которые попал пользователь.

Примеры получения экспериментов:

import SigmaSDK

guard let client = Sigma.getClient() else { return }

// Callback версии
client.getAllUserExperiments(
onSuccess: { experiments in
// Обработка строки вида "expId.userGroupIndex|expId.userGroupIndex|...", где `expId` - идентификатор эксперимента, `userGroupIndex` - индекс группы пользователя в эксперименте.
},
onError: { error in
// Обработка ошибки
}
)

client.getExperiment(
name: "my_first_experiment",
onSuccess: { experiment in
// Обработка эксперимента
},
onError: { error in
// Обработка ошибки
}
)

// Async-await версии
do {
let allExperiments = try await client.getAllUserExperiments()
// Обработка строки вида "expId.userGroupIndex|expId.userGroupIndex|...", где `expId` - идентификатор эксперимента, `userGroupIndex` - индекс группы пользователя в эксперименте.
} catch let error {
// Обработка ошибки
}

do {
let userExperiment = try await client.getExperiment(name: "my_first_experiment")
// Обработка эксперимента
} catch let error {
// Обработка ошибки
}

Получение информации из эксперимента

После получения эксперимента, информация об эксперименте доступна из методов объекта SigmaExperiment:

func getParamValue<T: SigmaPropertyType>(name: String) -> T?
func getFeatureFlagValue<T: SigmaPropertyType>(flagName: String) throws -> T?

Значение FeatureFlag или параметра эксперимента может быть типа Bool, Int, Double, String или [String: Any].

Получение holdout-экспериментов

Для получения всех holdout-экспериментов, в которые попал пользователь, используются следующие методы SigmaClient:

func getAllUserHoldouts(onSuccess: SigmaSuccessCallback<String?>?, onError: SigmaErrorCallback?)
func getAllUserHoldouts() async throws -> String?

Для получения информации, попал ли пользователь в конкретный holdout-эксперимент по названию, используются следующие методы SigmaClient:

func getHoldout(name: String, onSuccess: SigmaSuccessCallback<Bool>?, onError: SigmaErrorCallback?)
func getHoldout(name: String) async throws -> Bool

Примеры работы с holdout-экспериментами:

import SigmaSDK

guard let client = Sigma.getClient() else { return }

// Callback версии
client.getAllUserHoldouts(
onSuccess: { holdouts in
// Обработка строки вида "holdoutId.userGroupIndex|holdoutId.userGroupIndex|...", где `holdoutId` - идентификатор holdout-эксперимента, `userGroupIndex` - индекс группы пользователя в holdout-эксперименте (всегда 0).
},
onError: { error in
// Обработка ошибки
}
)

client.getHoldout(
name: "my_first_holdout",
onSuccess: { isInHoldout in
// Обработка информации, попал ли пользователь в holdout-эксперимент
},
onError: { error in
// Обработка ошибки
}
)

// Async-await версии
do {
let allHoldouts = try await client.getAllUserHoldouts()
// Обработка строки вида "holdoutId.userGroupIndex|holdoutId.userGroupIndex|...", где `holdoutId` - идентификатор holdout-эксперимента, `userGroupIndex` - индекс группы пользователя в holdout-эксперименте (всегда 0).
} catch let error {
// Обработка ошибки
}

do {
let holdout = try await client.getHoldout(name: "my_first_holdout")
// Обработка информации, попал ли пользователь в holdout-эксперимент
} catch let error {
// Обработка ошибки
}

Принудительное добавление пользователя в эксперимент (debug-only)

Для того, чтобы принудительно добавить пользователя в эксперимент, используются следующие методы SigmaClient:

func includeForce(experimentName: String, groupIndex: Int?, onSuccess: SigmaSuccessCallback<Void>?, onError: SigmaErrorCallback?)
func includeForce(experimentName: String, groupIndex: Int?) async throws
func includeForce(experimentName: String, onSuccess: SigmaSuccessCallback<Void>?, onError: SigmaErrorCallback?)
func includeForce(experimentName: String) async throws
func excludeForce(experimentName: String, onSuccess: SigmaSuccessCallback<Void>?, onError: SigmaErrorCallback?)
func excludeForce(experimentName: String) async throws
func excludeForceAll(onSuccess: SigmaSuccessCallback<Void>?, onError: SigmaErrorCallback?)
func excludeForceAll() async throws

Методы includeForce(...) принудительно добавят пользователя в forced user list эксперимента, если не передан groupIndex, или в forced user list группы эксперимента, если groupIndex передан (при наличии соответствующей группы в эксперименте). Результаты работы данных методов кэшируются и будут влиять на последующие запуски приложения (только на текущем устройстве). Для того, чтобы обратить действие данных методов, используются методы excludeForce(...) (для конкретного эксперимента) или excludeForceAll() (для всех экспериментов). Данные методы не предназначены для production, а должны быть использованы при тестировании через debug-меню или его аналоги. Данные методы имеют приоритет над forced user list, получаемом с сервера.

Примеры принудительного добавления пользователя в эксперимент:

import SigmaSDK

guard let client = Sigma.getClient() else { return }
let experimentName = "experiment_name"

// Callback версии
client.includeForce(
experimentName: experimentName,
onSuccess: {
// Пользователь добавлен в принудительный список
},
onError: { error in
// Произошла ошибка
}
)

client.excludeForce(
experimentName: experimentName,
onSuccess: { experiment in
// Пользователь исключен из принудительного списка
},
onError: { error in
// Произошла ошибка
}
)

// Async-await версии
do {
try await client.includeForce(experimentName: experimentName)
// Пользователь добавлен в принудительный список
} catch let error {
// Произошла ошибка
}

do {
try await client.excludeForce(experimentName: experimentName)
// Пользователь исключен из принудительного списка
} catch let error {
// Произошла ошибка
}

Работа с кэшем в SDK

Схема работы с кэшем в SDK

При вызове методов checkFlag, getAllUserExperiments, getExperiment из SigmaClient осуществляется проверка, валидный кэш или нет.

Кэш валиден при выполнении всех следующих условий:

  • Кэш существует
  • Кэш обновлялся в последний раз не раньше, чем cacheTTL от текущего времени

Если одно из условий не выполняется, кэш считается не валидным. Тогда методы, перечисленные выше, делают запрос конфига из сети, сохраняют его в кэш и после этого возвращают результат своей работы (значения экспериментов или флагов). Следующие вызванные методы во временных рамках cacheTTL не делают повторный запрос конфига из сети, потому что кэш валиден.

Бывают ситуации, когда запрос конфига из сети не удается по причинам, связанным с недоступностью интернета / недоступностью сервера. В таком случае, конфиг запрашивается retryCount раз. Если все попытки не удались, конфиг недоступен, кэш не валиден, методы checkFlag, getAllUserExperiments, getExperiment из SigmaClient вернут null. При повторном вызове данных методов, произойдет новая попытка запроса конфига по тем же правилам.

Кэш не обновляется самостоятельно в произвольные моменты времени; он обновляется только в момент вызова методов SigmaClient, если он не является валидным.

Минимально допустимое значение cacheTTL - 10 секунд, то есть успешно закэшированный конфиг не будет запрошен еще раз как минимум 10 секунд.

Только выше перечисленные методы взаимодействуют с кэшем. Последовательность их работы:

Метод проверяет, валиден ли кэш;

Если кэш не валиден, метод запрашивает конфиг с сервера и кэширует конфиг, если удалось загрузить;

Если не удалось загрузить конфиг несколько раз, метод возвращает null;

Если удалось достать валидный кэш (или загрузить новый конфиг, если кэш был не валиден), метод использует данные закэшированнаго кончика, чтобы вернуть запрашиваемые данные (значения экспериментов или флагов).