Arm1.ru

Пишем клиент для Яндекс.Метрики для iPhone

Добавлю, пожалуй, свой пост с Хабра. Пусть будет, да и к тому же он там в разделе "Я пиарюсь", который мало кто видит и он даже не индексируется поисковиками.

 

Как многие из вас, наверное, знают, у Яндекс.Метрики есть замечательный API, позволяющий получить данные по вашим счётчикам в XML или JSON. У меня давно уже чесались руки написать мобильный клиент, т.к. в течение дня я часто смотрю на цифры посещаемости ресурсов, которыми занимаюсь.

Существующие клиенты для iPhone мне показались ужасными и наконец руки перестали чесаться и дошли до написания.

Создание клиента можно разделить на следующие этапы:

  1. Регистрация приложения в Яндексе
  2. OAuth-авторизация пользователя и получение token'а
  3. Сохранение token'а
  4. Получение различных данных
  5. Отображение

Примеры создания графиков в PNChart есть на странице проекта на Github.>С регистрацией приложения всё понятно - добавляем приложение, отмечаем права, которые приложение хочет получить. При авторизации пользователь должен будет разрешить или отклонить разрешение на получение данных, да и будет видеть - какие права просит приложение.

 

OAuth-авторизация

Лично меня слегка подбешивает, когда ты устанавливаешь неофициальный клиент для какого-нибудь сервиса, а он просит ввести логин и пароль в свою форму. Мало ли куда они потом идут. По-моему, процедура авторизации выглядит более прозрачно, если всплывает окно браузера с авторизацией на сайте сервиса.

Технически OAuth-авторизация делается довольно просто - добавляем в приложение собственную URL-Scheme (например, myapp), в настройках приложения на сайте Яндекса (там, где регистрировали приложение) указываем в качестве Callback URI что-то вроде myapp://, после чего кодим, чтобы после авторизации приложение пыталось открыть url по адресу myapp://, таким образом url откроется в нашем приложении, а в нём уже все данные, включая access_token.
На Хабре есть отличная статья именно с такой авторизацией и именно для Яндекса, которая сэкономила мне время, поэтому особо больше описывать авторизацию не буду, чтобы не повторяться. После авторизации у нас есть token, который мы и сохраним.

Технический нюанс: логично, что когда-нибудь пользователь захочет разлогиниться в приложении. Чтобы кто-нибудь не имел доступа к статистике кроме него, или, например, чтобы залогиниться под другим аккаунтом. При авторизации через браузер (то есть, через UIWebView), мало удалить полученный ранее token. Ваша авторизация в UIWebView запоминается, поэтому при разлогинивании пользователя нужно почистить cookies в песочнице приложения, иначе при авторзации вы автоматически залогинитесь в тот же аккаунт. Простого кода в моём случае было достаточно:

 NSHTTPCookie *cookie;
 NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
 for (cookie in [storage cookies]) {
     [storage deleteCookie:cookie];
 }
 [NSUserDefaults standardUserDefaults] synchronize]; 

Сохранение token'а

Важный момент для параноиков - сохранение полученного token. Можно, конечно, положить его в NSUserDefaults - но говорят, при наличии, например, jailbreak, это не безопасно и можно расковырять этот token. Хорошая практика - хранить ключи доступа, логины и прочие важные штуки в KeyChain, там всё зашифровано и секьюрно.

Ковыряя его возможности в первый раз, даже со статьями на родном языке, возникает лёгкое желание побиться головой обо что-нибудь. Разбираться в этом всём, когда тебе нужно просто сохранить n-символов, а потом их оттуда взять, очень не хочется. Для ленивых вроде меня, Apple к документации приложила класс KeychainItemWrapper. Название само за себя говорит - обёрка вокруг KeyChain. Правда, класс этот написан давно и без поддержки ARC, но на github легко можно найти его форк с поддержкой ARC. Вот этот, например (его ещё и через CocoaPods можно подключить).

Ну а теперь сохранение и удаление token'а сводится к нескольким строчкам:

 // сохранение
 KeychainItemWrapper *keychainWrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"OAuthToken" accessGroup:nil];
 [keychainWrapper setObject:@"accountName" forKey:(__bridge id)(kSecAttrAccount)];
 [keychainWrapper setObject:tokenToSave forKey:(__bridge id)(kSecValueData)]; 
 // удаление
 KeychainItemWrapper *keychainWrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"OAuthToken" accessGroup:nil];
 [keychainWrapper resetKeychainItem]; 

Получение различных данных

 Лично мне больше нравится получать данные в JSON. С запросами всё довольно просто - шлём нужный запрос к методу из API, в заголовок включаем Authorization: OAuth . Кстати, token можно и как get-параметр передавать, например:
https://api-metrika.yandex.ru/counters?oauth_token=

Раз уж мы параноим с самого начала насчёт безопасности, то запросы все можно слать через https (но можно и через http). Получаем данные в JSON, парсим их через встроенный NSJSONSerialization:

 dataObject = [NSJSONSerialization JSONObjectWithData:result options:NSJSONReadingMutableContainers error:&e]; 

Результат, например, для получения списка счётчиков:

 айМетрик

Ну а дальше полученные данные нужно как-то отобразить.

Отображение

Если смотреть на web-интерфейс Метрики, то там в основном 2 представления данных - таблицы с текстовыми данными и графики.

С таблицами всё, вроде, понятно - используем UITableView. Почти все данные содержат одни и те же общие показатели - просмотры, визиты, уникальные посетители, глубина просмотра, время на сайте, показатель отказов. Поэтому я сделал класс-потомок UITableViewCell, чтобы не плодить кучу одинакового кода в каждом из UIViewController, после чего в методе
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
мы просто создаём ячейку с уже полученными данными, например:

 NSString *CellIdentifier = [NSString stringWithFormat:@"CellId_%li_%li", (long)indexPath.section, (long)indexPath.row];
 SourcesTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 if ( cell == nil ) {
     cell = [[SourcesTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier
     textOnLabel:@"просмотры" labelTextColor:[UIColor black]];
 }
 
 // задаём значения
 [cell 
     changeValuesWithpageViewsNum:pageViewsNum 
     visitTime:visitTime 
     denials:denialsValue 
     visitsNum:visitsNum
 ]; 

Время, проведённое на сайте, кстати, возвращается в секундах, поэтому его ещё надо красиво вывести в формате мм:сс, а процент отказов - от 0 до 1, поэтому 50% вернётся как 0.5.

Для рисования графиков есть несколько готовых решений. Кое что, опять же, можно найти на Хабре. Лично мне на гитхабе приглянулся PNChart - минималистично, симпатично. Подключаем через CocoaPods и в путь.



 

Примеры создания графиков в PNChart есть на странице проекта на Github.

Компонуем всё в UITabsViewController, немножко оформляем и получаем на выходе быструю статистику в кармане.

Результат я решил выгрузить в AppStore, желающие могут установить.

Для стимула продолжать разработку, сделал приложение платным.

Полезные ссылки:
CocоaPods — мощное средство в руках Objective-C разработчика
Яндекс OAuth авторизация в iOS
PNChart (рисование графиков)
KeychainItemWrapper (обёртка вокруг KeyChain)

keyboard_return back