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


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

    У меня давно уже чесались руки написать мобильный клиент, т.к. в течение дня я часто смотрю на цифры посещаемости ресурсов, которыми занимаюсь.

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

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

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

    Регистрация


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

    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 <access_token>. Кстати, token можно и как get-параметр передавать, например:
    api-metrika.yandex.ru/counters?oauth_token=<access_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 и в путь.
    image
    Примеры создания графиков в PNChart есть на странице проекта на Github.

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


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

    Для стимула продолжать разработку, сделал приложение платным. Прилагаю 10 промокодов:
    A49RNFA4W6KR
    37LWRW7TYH36
    RMPHML3EL6EL
    JKWWFATEJ4YE
    4RETFRLEPRFW
    AWKN4LYRM4LK
    L4K6NJNTN4RJ
    NT9Y97HN7HKA
    TRPXHFXX47PE
    E96MN3JP9X9Y

    Полезные ссылки:
    CocоaPods — мощное средство в руках Objective-C разработчика
    Яндекс OAuth авторизация в iOS
    PNChart (рисование графиков)
    KeychainItemWrapper (обёртка вокруг KeyChain)
    • +16
    • 3,2k
    • 8
    Поддержать автора
    Поделиться публикацией

    Комментарии 8

      +1
      Все коды уже недействительны и хоть бы кто отписал, что забрал.
        +3
        А чем отличается от Миллиметрика?
          0
          У миллиметрики функционал шире, полностью бесплатное, и, на мой субъективный взгляд, дизйан приятнее.
            0
            Минусует видимо автор приложения?))
            –2
            Вышло 24 марта… Вполне понятно, почему я его не видел, я примерно в этот день свой апп в App Store отправлял.
            Судя по всему, отличается как минимум нативностью :) Status Bar нету, приложение убить пришлось т.к. страница авторизации не прогрузилась, а кнопки «назад» там вообще нигде не предусмотрено. Что-то тут не так :)
            +1
            NSHTTPCookie *cookie;
            NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
            for (cookie in [storage cookies]) {
            [storage deleteCookie:cookie];
            }
            [NSUserDefaults standardUserDefaults] synchronize];


            Я бы чистил только «нужные» куки, ведь там может быть еще полезный кеш кроме сессии. И зачем здесь синхронизаия дефолтсов?:)
            Насчет Keychain. Не забыл при первом старте их чистить? Данные же там остаются даже после удаления аппа.

            Приложение крутое вышло, буду пользовать!
              0
              А версии для iPad не планируется?

              Проги типа этой лучше делать бесплатными, с анлоком функций или снятием ограничений через in-app, чтобы люди могли без проблем потестировать продукт прежде чем платить деньги. Например, можно было бы сделать бесплатную версию, поддерживающую только статистику по одному сайту. А через инапп бы можно было бы разблокировать доступ к статистике всех остальных сайтов в аккаунте Яндекс.Метрики.

              А сколько времени на разработку ушло?
                0
                Скорее всего под iPad сделаю, но сначала допилю под iPhone, благо обновление уже готово.

                Времени ушло около 2 недель (писал в свободное от работы время).

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое