Добрый вечер!
Всё началось с того, что необходим был более или менее удобный инструмент для работы с API социальной сети ВКонтакте под iOS. Однако Google меня достаточно быстро расстроил результатами поиска:
- StonewHawk — GitHub
- maiorov/VKAPI — GitHub
- AndrewShmig/Vkontakte-iOS-SDK (да-да, с первой версией было не всё так хорошо, как могло показаться с первого взгляда)
Вроде бы всё хорошо, самое главное есть, но вот использование не вызывает приятных ощущений.
Под катом я расскажу, как работает обновленная версия ВКонтакте iOS SDK v2, с чего всё начиналось и к чему в итоге пришли.
Введение
Вторая версия кардинально отличается от первой и той, которая интегрирована в ASASocialServices.
Для того, чтобы лучше понять, что же изменилось после выхода первой версии, надо понять в чем были основные особенности/недостатки первой версии:
- Отсутствие возможности кэшировать запросы
- Запросы осуществлялись синхронно
- Отсутствие статуса загрузки файлов на сервер ВК
- Отсутствие возможности работать с несколькими пользователями (имеется ввиду хранение и повторное использование авторизационных данных вроде токена доступа)
- Не было возможности выйти из текущей авторизованной учетной записи пользователя.
Во второй версии многие недостатки, а точнее все, были устранены (возможно появились новые, но и их мы со временем постараемся исправить и устранить) и теперь есть следующие возможности/фичи в SDK:
- хранилище, которое позволяет работать со множеством пользователей
- кэширование запросов для каждого пользователя отдельно
- логаут последнего авторизовавшегося пользователя
- запросы теперь представляют собой отдельные классы, которые можно использовать и настраивать под себя (имеется связь запросов с хранилищем)
- запросы используют теперь делегаты для уведомления о состоянии запроса (использование блоков рассматривается, но не более)
- возможность отслеживать статус загрузки файла на сервер ВК
- пользователь теперь представляет собой объект в котором инкапсулированы все действия, которые он может совершить (вступить в группу, оставить сообщение на стене и тд)
- документация, которая во второй версии стала лучше, полноценнее и в которой теперь можно будет найти какие-то интересные особенности в работе классов/методов
Из планов на будущее можно выделить следующие пункты:
- Реализация класса типа VKRequest, который будет уже оперировать с блоками. У программиста будет возможность выбрать и использовать либо запросы+делегаты, либо запросы+блоки.
- Усовершенствовать хранилище и добавить возможность программисту указать своё хранилище, которое будет отвечать неким обязательным методам протокола VKStorageDataDelegate+VKStorageCacheDataDelegate.
- Реализовать возможность автообновления данных кэша определенных запросов. Выглядеть это может примерно таким образом: в бэкграунде, через указанные интервалы времени будет осуществляться некий запрос, который в случае успеха обновит данные кэша, тем самым ускорив последующие получения данных по этому запросу.
- Хранение пользовательских куки для реализации автоматического входа и обновления токена доступа.
- Добавление еще одного метода в VKConnectorDelegate, который будет вызываться при необходимости осуществления любых пользовательских действий (ввода капчи, ввод данных при авторизации приложения и тд)
- ...
Хранилище
Хранилище (VKStorage
) задумывалось, как некий пункт управления данными авторизованных пользователей.
Хранилище может выполнять следующие действия:
- Добавить новый объект
- Удалить определенный объект
- Удалить все данные
- Удалить все данные кэша
- Получить объект хранения по указанному пользовательскому идентификатору («ключами» в хранилище являются именно пользовательские уникальные идентификаторы)
- Получить все объекты, которые находятся в хранилище
Хранилище состоит из объектов хранилища — VKStorageItem
, который является не более, чем объектом содержащим два поля — пользовательский токен доступа и объект кэша.
Для инициализации объекта хранилища достаточно указать пользовательский токен доступа и основную директорию для кэша (не конкретно для данного пользователя, а вообще).
Токены доступа хранилище хранит в NSUserDefaults
, а вот данные кэша в NSCachesDirectory
.
Интерфейс класса VKStorage
.
Чаще обращайтесь к документации, если возникают какие-то вопросы или вы в чем-то сомневаетесь:
Кэширование данных
При реализации механизма кэширования запросов хотелось получить нечто удобное, простое и гибкое.
За кэширование пользовательских запросов отвечает класс VKCachedData
.
Экземпляр класса инициализируется директорией в которой будут храниться кэши запросов (в данном случае путь к директории должен быть уникальным для каждого пользователя).
Какие действия можно осуществлять:
- Добавить определенные данные для указанного NSURL в кэш (причин использовать именно NSURL, а не NSURLRequest было несколько, а основной причиной был тот факт, что в ВК все запросы осуществляются методом GET + никаких заголовков дополнительных не передается, POST же запросы не кэшируются)
Текущий вид может быть подвергнут пересмотру и изменению - Добавить определенные данные для указанного NSURL в кэш с указанным временем жизни.
Возможные значения времени жизни кэша:typedef enum { VKCachedDataLiveTimeNever = 0, VKCachedDataLiveTimeOneMinute = 60, VKCachedDataLiveTimeThreeMinutes = 3 * 60, VKCachedDataLiveTimeFiveMinutes = 5 * 60, VKCachedDataLiveTimeOneHour = 1 * 60 * 60, VKCachedDataLiveTimeFiveHours = 5 * 60 * 60, VKCachedDataLiveTimeOneDay = 24 * 60 * 60, VKCachedDataLiveTimeOneWeek = 7 * 24 * 60 * 60, VKCachedDataLiveTimeOneMonth = 30 * 7 * 24 * 60 * 60, VKCachedDataLiveTimeOneYear = 365 * 30 * 7 * 24 * 60 * 60, } VKCachedDataLiveTime;
По умолчанию для всех запросов время жизни кэша составляет один час.
- Удалить кэш для указанного
NSURL
- Удаление директории кэширования
- Удаление всех кэшей (без удаления директории в которой находятся данные)
- Получить закэшированные данные по указанному
NSURL
(обрабатывать возвращаемые данные можно в стандартном режиме, либо в оффлайн режиме)
При разработке столкнулся со следующей проблемой: если при каждой новой авторизации пользователь будет обновлять свой токен доступа, то каждый раз кэш запроса будет храниться под другим именем (имя кэша представляет собой MD5 хэш строки запроса), хотя логически-то запрос один и то же, а следовательно при повторном вызове данные будут перезаписываться или заново запрашиваться из сети.
Решение оказалось в исключение токен доступа из NSURL
при сохранении данных в кэш. Таким образом одинаковые запросы, но с разными пользовательскими токенами доступа будут иметь один файл кэша.
Теперь опишу о том, зачем и почему была введена фича offlineMove
(оффлайн режим запросов): представил себе обычную ситуацию, когда отсутствует сеть, но данные в кэше есть, а значит их надо вернуть, чтобы не вводить какие-то дополнительные сообщения об ошибках или простого возврата nil. Особенностью оффлайн режима является тот факт, что даже если запрошенные данные кэша и устарели (их время жизни истекло), то они будут возвращены и не произойдет их удаления до тех пор, пока не появиться сеть и очередной такой же запрос не обновит данные.
Если нет надобности использовать кэширование запросов, то устанавливайте время жизни кэша запроса в VKCachedDataLiveTimeNever
.
Каждый запрос содержит поле cacheLiveTime
, которое является временем жизни кэша данных только текущего запроса. По умолчанию время жизни кэша данных устанавливается равным одному часу.
Кэширование изображений, аудио, видео
Как такового, отдельного метода для кэширования изображений/аудио/видео нет, но это легко обойти следующим образом (покажем на примере):
NSUInteger currentUserID = [VKUser currentUser].accessToken.userID;
VKStorageItem *item = [[VKStorage sharedStorage]
storageItemForUserID:currentUserID];
NSString *mp3Link = @"https://mp3.vk.com/music/pop/j-lo.mp3";
NSURL *mp3URL = [NSURL URLWithString:mp3Link];
NSData *mp3Data = //mp3 data from request
[item.cachedData addCachedData:mp3Data
forURL:mp3URL
liveTime:VKCachedDataLiveTimeOneMonth];
Указанный трек будет храниться в течение недели, либо пока система не очистить папку кэша за надобностью свободного места, либо вы сами этого не сделаете.
Коннектор
Основная роль VKConnector
заключается в получении пользовательского токена доступа и его сохранение в хранилище.
Запустить процесс получения пользовательского токена доступа можно следующим образом:
[[VKConnector sharedInstance] startWithAppID:@"12345" permissions:@[@"friends", @"wall", @"groups"]];
После вызова данного метода перед пользователем появится такое вот окно авторизации:
После того, как пользователь введет свои данные и нажмет «Войти»:
Окно исчезнет с плавной анимацией.
Видео пользовательской авторизации.
Коннектор позволяет произвести логаут последнего авторизовавшегося пользователя методом logout
.
VKConnector
позволяет программисту следить за тем на каком этапе сейчас авторизация пользователя, будет ли отображено окно авторизации, будет ли окно скрыто, удалось ли обновить токен доступа или нет, устарел ли токен доступа или нет и тд. Для получения подробной информации смотрите VKConnectorDelegate
.
Так же стоит отметить, что при повторной авторизации пользователя его локальный токен доступа (находящийся в хранилище) обновляется и сохраняется для осуществления дальнейших запросов.
Запросы
Запросы являются на мой взгляд самым интересным реализованным классом (после кэша данных :) ).
В целом они позволяют осуществлять запросы не только к социальной сети ВКонтакте (нет жесткой привязки), но так же не исключают удобные методы для работы именно с этой социальной сетью:
- (instancetype)initWithMethod:(NSString *)methodName
options:(NSDictionary *)options;
Описание метода можно найти в документации.
Запросы являются асинхронными и работают с делегатами. Делегаты должны соответствовать протоколу VKRequestDelegate
в котором лишь один метод является обязательным — метод обработки ответа сервера.
В качестве опциональных методов являются:
- обработка сообщения об ошибке соединения
- обработка сообщения об ошибка парсинга ответа сервера из JSON формата в Foundation объект
- статус загрузки данных
- статус отправки данных
У каждого запроса есть несколько очень полезных свойств:
- Подпись (signature)
- Время жизни кэша для текущего запроса (cacheLiveTime)
- Оффлайн режим запроса (offlineMode)
Рассмотрим каждое свойство подробней:
- Подпись (signature)
Подписью объекта может быть любой объект, как словарь неких связанных данных, так и просто строка. Подобное было реализовано с мыслью о том, что при обработке одним классом нескольких запросов (получения друзей и личной информации о каждом из пользователей) необходимо неким образом отличать один запрос от другого. Делать подпись фиксированной было бы неправильно и неудобно, поэтому было решено оставить выбор на усмотрение программиста.
Стоит знать, при осуществлении запросов из классаVKUser
, что каждый запрос подписан строкой селектора вызвавшего его метода. - Время жизни кэша для текущего запроса (cacheLiveTime)
Время существования кэша для данного запроса определяется именно этим свойством. Некоторые данные меняются очень часто (лента новостей, стена пользователя), а значит было бы неправильно использовать агрессивное кэширование и хранить данные максимально долго, но вот другие запросы, такие, например, как список друзей пользователя, список групп, список подписчиков и тд, могут спокойно жить в течение нескольких дней в кэше.
Если происходит запрос к некому файлу/аудиозаписи/видеозаписи, то подобные вещи могут храниться очень долго и не меняться, поэтому можно использоваться для них максимальное время жизни в один год (VKCachedDataLiveTimeOneYear
). - Оффлайн режим запроса (
offlineMode
)
Оффлайн режим непосредственно влияет на то, каким образом будут возвращены/сохранены данные кэша. При отсутствии интернет соединения настоятельно рекомендуется использовать данный режим. Он позволит «продлить» время жизни данным из кэша, которые при стандартном режиме должны были бы быть удалены.
При создании запроса его выполнение начинается не сразу, а после вызова start
.
Рассмотрим на примере:
VKRequest *infoRequest = [VKRequest requestMethod:@"users.get" options:@{} delegate:self];
//возможная настройка свойств
infoRequest.cacheLiveTime = VKCachedDataLiveTimeOneMonth;
infoRequest.signature = @"Some signature string";
//какие-то действия
[infoRequest start]; // запрос стартовал
Вы спокойно можете отметить запрос, если у вас есть переменная, которая на него ссылается, либо отменить в одном из методов делегата VKRequestDelegate
.
Пример:
[infoRequest start]; // запрос стартовал
// какие-то действия
// надо отменить запрос
[infoRequest cancel];
Самым расширенным методом является:
+ (instancetype)requestHTTPMethod:(NSString *)httpMethod
URL:(NSURL *)url
headers:(NSDictionary *)headers
body:(NSData *)body
delegate:(id <VKRequestDelegate>)delegate;
Как видите данный метод позволяет настроит запрос к серверу с максимальным числом значимых параметров.
Может быть полезен при реализации загрузок аудио/видео на сервер.
Вот так выглядит описание запроса (пример) возвращаемое методом description
:
{
cacheLiveTime = 3600;
delegate = "<ASAAppDelegate: 0x752c540>";
offlineMode = NO;
request = "<NSMutableURLRequest https://api.vk.com/method/users.getFollowers>";
signature = "followersWithCustomOptions:";
}
Пользователь
Пользовательский класс позволяет осуществлять запросы от лица текущего активного пользователя.
Рассмотрим несколько методов класса, которые играют важную роль при работе с несколькими пользователями в приложении:
-
+ (instancetype)currentUser
-
+ (BOOL)activateUserWithID:(NSUInteger)userID
-
+ (NSArray *)localUsers
Рассмотрим подробнее.
-
+ (instancetype)currentUser
Получение текущего активного пользователя. Вот как описан этот метод в документации:
-
+ (BOOL)activateUserWithID:(NSUInteger)userID
Делает активным пользователя с указанным пользовательским идентификатором. Полезный метод, если ваше приложение планирует подключать несколько учетных записей пользователя. -
+ (NSArray *)localUsers
Получение списка пользователей находящихся в хранилище. В массиве будут только пользовательские идентификаторы.
Теперь поговорим о свойствах, которые тоже играют важную роль в том, как будут обрабатываться результаты запросов и сами запросы.
- Начинать выполнение запросов немедленно
По умолчанию все запросы начинают своё выполнение немедленно после вызова метода в котором они были созданы. Для того, чтобы получить управление над стартом/отменой выполнением запросов необходимо поступить следующим образом:
[VKUser currentUser].startAllRequestsImmediately = NO; // работает для всех последующих запросов, а не только для одного VKRequest *infoRequest = [[VKUser currentUser] infoWithCustomOptions:@{@"uids": @"1"}]; // какие-то действия [infoRequest start];
Если хотите только одним запросом управлять, то можно поступить следующим образом:
[VKUser currentUser].startAllRequestsImmediately = NO; VKRequest *infoRequest = [[VKUser currentUser] infoWithCustomOptions:@{....}]; [VKUser currentUser].startAllRequestsImmediately = YES; //какие-то действия [[VKUser currentUser] groupsJoinWithCustomOptions:@{...}]; // выполнение запроса стартует немедленно
- Оффлайн режим
Позволяет осуществлять все последующие запросы в оффлайн режиме, режиме, когда данные кэша не удаляются даже в случае истечения их срока жизни.
Пример получения пользователей находящихся в определенной группе:
[VKUser currentUser].delegate = self;
[[VKUser currentUser] groupsGetMembersWithCustomOptions:@{
@"group_id": @"1",
@"count" : @"100",
@"offset": @"0"
}];
Документация
Документацию я всегда стараюсь поддерживать в актуальном состоянии и писать о возможных особенностях использования классов, методов, свойств.
Считаю, что без хорошей документации у программиста не будет особого желания разбираться в чужом коде и вообще использовать SDK.
Несколько скриншотов текущей документации, которые отразят всю её полноту и целостность:
В завершение
Статья, как мне кажется, получилась достаточно длинной, так что на этом пока остановлюсь. Упомянул о многом и старался описывать таким образом, чтобы у Вас сложилось целостная картина.
Хочу отметить, что проект активно развивается и поддерживается.
Найти самую актуальную версию можно по этой ссылке: GitHub ( github.com/AndrewShmig/Vkontakte-iOS-SDK-v2.0 )
Автор: AndrewShmig