Долго думал, стоит ли писать данную статью здесь, как отнесется к этому сообщество и если писать, то в какой хаб должно быть адресовано. В конце концов решил, что некоторым разработчикам будет интересно рассмотреть пример возможно провального проекта, который изначально показался перспективным.
Наши заблуждения стоят нам напрасно потерянного времени!
Итак, кому интересно, добро пожаловать под кат!
Краткая предыстория
Для того, чтобы выбрать себе перспективное направление развития, я обычно делаю несколько рабочих прототипов приложений различной тематики так, чтобы их можно было залить в App Store и наблюдать за динамикой развития. Наиболее динамичный проект по установкам и активности пользователей, принимается за базовый и расширяется.
Обязательное условие при создании прототипа, он не должен повторять или копировать существующие приложения в сторе.
Почему так? Создание рабочих прототипов займет меньше времени, чем работа с громоздким проектом, который может оказаться никому не нужным очередным приложением в App Store.
Одним из таких прототипов и являлось приложение “Clean for VK”, которое без дополнительных манипуляций по его развитию, показывало хорошее число установок в сторе.
Прототип был сделан буквально “на коленке” в течении пары дней и только и умел, что чистить профиль пользователя социальной сети ВК от забаненных френдов и подписчиков.
Сейчас по идее, в меня должны полететь камни, что еще мол один открыл для себя SDK социальной сети ВК).
Не совсем так, чуть дальше я покажу один “костыль”, не имеющий никакого отношения к SDK.
Выдержано ли было условие “не повторять и не копировать” из магазина приложений? Выдержано на 100%! На тот момент, App Store пестрил мессенджерами всех мастей и видов для ВК, различными “шпиЁнами” для неё же, но страницу/профиль пользователя не чистило ни одно приложение.
Откуда уши торчат?
А ушки торчат с того самого времени, как я пробовал продвигать свои продукты через эту социальную сеть. Мой профиль активно раскручивался за счет таких же как и я пользователей и в конце концов размер друзей превысил 3000 человек. Все бы ничего, но со временем часть френдов превратилась в “социальных собак”, как ВК маркирует забаненных или удаленных пользователей. Кому то может и все равно, но хоть страница изначально задумывалась как рабочая, такое количество “собачек” сильно мозолило глаза, что я иногда предпринимал попытки избавиться от них вручную.
Ох и хлопоное это дело)! Вот тогда и решено было быстро накидать для себя программку по автоматизации чистки своей страницы. А уже после, решил принять ее за прототип и выкинуть на публикацию в стор.
О конкурентах
Обычное дело, что у современных разработчиков, плохо не только с английским языком, но и с креативом! Так и в случае моего приложения. На момент создания прототипа, оно было единственным в своем роде. Но стоило ему появиться в сторе, как через какое-то время появилась пара программ со схожим функционалом. С одной стороны это плохо, так как появились те, к кому может уйти потенциальный пользователь, а с другой стороны, конкуренция делает нас лучше и заставляет развиваться.
Когда прототип стал базовым и почему?
Сделать приложение базовым было решено в тот момент, когда количество активных установок превысило 10000 и появилась уверенность, что программу будут устанавливать и далее, даже при наличии образовавшихся конкурентов. На тот момент пришло понимание, что несмотря на попадание программы в ТОП платных социальной категории российского сегмента App Store, конверсия приложения оставляет желать лучшего и его нужно развивать!
Особенности реализации
При разработке я стараюсь использовать NSOperation*, чтобы можно было более наглядно управлять очередью операций, GCD практически не использую, но иногда при быстрой правке метода могу воткнуть и GCD. Здесь и далее подразумевается, что все методы в приложении завернуты в NSOperation, но подробно многопоточность я рассматривать не буду.
Кстати, огромное спасибо Егору из Rambler&Co за недавний доклад по NSOperation. Было познавательно и интересно!
В основном, практически весь функционал строится на официальном SDK, который позволяет покрыть 90% потребностей в разработке для данной сети.
users.get
users.search
users.isAppUser
users.getFollowers
users.report
users.getNearby
Авторизация
auth.checkPhone
auth.signup
auth.confirm
auth.restore
Стена
wall.get
wall.search
wall.getById
wall.post
wall.repost
wall.getReposts
wall.edit
wall.delete
wall.restore
wall.pin
wall.unpin
wall.getComments
wall.addComment
wall.editComment
wall.deleteComment
wall.restoreComment
wall.reportPost
wall.reportComment
Фотографии
photos.create
photos.editAlbum
photos.getAlbums
photos.get
photos.getAlbumsCount
photos.getById
photos.getUploadServer
photos.getOwnerPhotoUploadServer
photos.getChatUploadServer
photos.saveOwnerPhoto
photos.saveWallPhoto
photos.getWallUploadServer
photos.getMessagesUploadServer
photos.saveMessagesPhoto
photos.report
photos.reportComment
photos.search
photos.save
photos.copy
photos.edit
photos.move
photos.makeCover
photos.reorderAlbums
photos.reorderPhotos
photos.getAll
photos.getUserPhotos
photos.deleteAlbum
photos.delete
photos.restore
photos.confirmTag
photos.getComments
photos.getAllComments
photos.createComment
photos.deleteComment
photos.restoreComment
photos.editComment
photos.getTags
photos.putTag
photos.removeTag
photos.getNewTags
Друзья
friends.get
friends.getOnline
friends.getMutual
friends.getRecent
friends.getRequests
friends.add
friends.edit
friends.delete
friends.getLists
friends.addList
friends.editList
friends.deleteList
friends.getAppUsers
friends.getByPhones
friends.deleteAllRequests
friends.getSuggestions
friends.areFriends
friends.getAvailableForCall
Виджеты
widgets.getComments
widgets.getPages
Работа с данными
storage.get
storage.set
storage.getKeys
Статус
status.get
status.set
Аудиозаписи
audio.get
audio.getById
audio.getLyrics
audio.search
audio.getUploadServer
audio.save
audio.add
audio.delete
audio.edit
audio.reorder
audio.restore
audio.getAlbums
audio.addAlbum
audio.editAlbum
audio.deleteAlbum
audio.moveToAlbum
audio.setBroadcast
audio.getBroadcastList
audio.getRecommendations
audio.getPopular
audio.getCount
Страницы
pages.get
pages.save
pages.saveAccess
pages.getHistory
pages.getTitles
pages.getVersion
pages.parseWiki
pages.clearCache
Группы
groups.isMember
groups.getById
groups.get
groups.getMembers
groups.join
groups.leave
groups.search
groups.getInvites
groups.banUser
groups.unbanUser
groups.getBanned
groups.edit
groups.editPlace
groups.getSettings
groups.getRequests
groups.editManager
groups.addLink
groups.deleteLink
groups.editLink
groups.reorderLink
groups.removeUser
groups.approveRequest
Обсуждения
board.getTopics
board.getComments
board.addTopic
board.addComment
board.deleteTopic
board.editTopic
board.editComment
board.restoreComment
board.deleteComment
board.openTopic
board.closeTopic
board.fixTopic
board.unfixTopic
Видеозаписи
video.get
video.edit
video.add
video.save
video.delete
video.restore
video.search
video.getUserVideos
video.getAlbums
video.getAlbumById
video.addAlbum
video.editAlbum
video.deleteAlbum
video.reorderAlbums
video.reorderVideos
video.addToAlbum
video.removeFromAlbum
video.getAlbumsByVideo
video.getComments
video.createComment
video.deleteComment
video.restoreComment
video.editComment
video.getTags
video.putTag
video.removeTag
video.getNewTags
video.report
video.reportComment
Заметки
notes.get
notes.getById
notes.getFriendsNotes
notes.add
notes.edit
notes.delete
notes.getComments
notes.createComment
notes.editComment
notes.deleteComment
notes.restoreComment
Места
places.add
places.getById
places.search
places.checkin
places.getCheckins
places.getTypes
Аккаунт
account.getCounters
account.setNameInMenu
account.setOnline
account.setOffline
account.lookupContacts
account.registerDevice
account.unregisterDevice
account.setSilenceMode
account.getPushSettings
account.setPushSettings
account.getAppPermissions
account.getActiveOffers
account.banUser
account.unbanUser
account.getBanned
account.getInfo
account.setInfo
account.changePassword
account.getProfileInfo
account.saveProfileInfo
Сообщения
messages.get
messages.getDialogs
messages.getById
messages.search
messages.getHistory
messages.send
messages.delete
messages.deleteDialog
messages.restore
messages.markAsRead
messages.markAsImportant
messages.getLongPollServer
messages.getLongPollHistory
messages.getChat
messages.createChat
messages.editChat
messages.getChatUsers
messages.setActivity
messages.searchDialogs
messages.addChatUser
messages.removeChatUser
messages.getLastActivity
messages.setChatPhoto
messages.deleteChatPhoto
Новости
newsfeed.get
newsfeed.getRecommended
newsfeed.getComments
newsfeed.getMentions
newsfeed.getBanned
newsfeed.addBan
newsfeed.deleteBan
newsfeed.ignoreItem
newsfeed.unignoreItem
newsfeed.search
newsfeed.getLists
newsfeed.saveList
newsfeed.deleteList
newsfeed.unsubscribe
newsfeed.getSuggestedSources
Мне нравится
likes.getList
likes.add
likes.delete
likes.isLiked
Опросы
polls.getById
polls.addVote
polls.deleteVote
polls.getVoters
polls.create
polls.edit
Документы
docs.get
docs.getById
docs.getUploadServer
docs.getWallUploadServer
docs.save
docs.delete
docs.add
Закладки
fave.getUsers
fave.getPhotos
fave.getPosts
fave.getVideos
fave.getLinks
fave.addUser
fave.removeUser
fave.addGroup
fave.removeGroup
fave.addLink
fave.removeLink
Уведомления
notifications.get
notifications.markAsViewed
Статистика
stats.get
stats.trackVisitor
stats.getPostReach
Поиск
search.getHints
Приложения
apps.getCatalog
apps.get
apps.sendRequest
apps.deleteAppRequests
apps.getFriendsList
apps.getLeaderboar
apps.getScoreМетод
Служебные
utils.checkLink
utils.resolveScreenName
utils.getServerTime
Данные ВК
database.getCountries
database.getRegions
database.getStreetsById
database.getCountriesById
database.getCities
database.getCitiesById
database.getUniversities
database.getSchools
database.getSchoolClasses
database.getFaculties
database.getChairs
Подарки
gifts.get
other
execute
Так, получение друзей пользователя для анализа и последующей обработки, решается достаточно просто:
NSInteger offset = 0;
NSMutableArray *friends = [NSMutableArray new];
NSMutableArray *banFriends = [NSMutableArray new];
NSArray *array = nil;
do {
array = [self requestFriendsWithOffset:offset];
[friends addObjectsFromArray:array];
offset += 5000;
} while (array.count);
for (int i = 0; i < friends.count; i++) {
if (friends[i][@"deactivated"]) {
[banFriends addObject:friends[i]];
}
}
Сам метод получения друзей
- (NSArray *)requestFriendsWithOffset:(NSInteger)offset {
__block NSArray* responseResult = @[];
VKRequest *request = [[VKApi friends] get:@{@"offset": @(offset),
@"count": @"5000",
@"fields": @"deactivated"}];
request.waitUntilDone = YES;
[request executeWithResultBlock:^(VKResponse *response) {
NSArray* items = response.json[@"items"];
if ([items count] < 1) {
responseResult = @[];
} else {
responseResult = items;
}
} errorBlock:^(NSError *error) {
NSLog(@"ERROR FRINDS REQUEST %@", error.localizedDescription);
}];
return responseResult;
} // requestFriendsWithOffset
Единственное, что пришлось освоить метод “execute”, для ускорения работы приложения при выборке контента. Сам метод использует в своей работе VKScript, как аналог JavaScript. Использовать его достаточно просто и эффективно, но так получилось, что внятных ответов по его использованию у официального сообщества ВК получить не удалось.
Пример подготовки запроса для execute
- (NSString *)codeForComments:(NSArray *)array
andWithOffset:(NSInteger)offset {
NSMutableString *codeString = [NSMutableString new];
if (array.count > 0) {
NSMutableString *tempString = [NSMutableString new];
for (int i = 0; i < array.count; i++) {
NSString *string = [NSString stringWithFormat:@" ,API.wall.getComments({\"post_id\":\"%@\",\"count\":\"100\",\"extended\":\"1\",\"offset\":\"%@\"})", [array[i] stringValue], [NSString stringWithFormat:@"%ld", (long)offset]];
[tempString appendString:string];
}
NSString * extractString = [tempString substringFromIndex:2];
[codeString appendString:@"return ["];
[codeString appendString:extractString];
[codeString appendString:@"];"];
}
return [NSString stringWithFormat:@"%@", codeString];
} // codeForComments
Сам запрос при выполнении
- (NSArray *)executeCommentsForPosts:(NSString *)codeString {
NSDictionary *parametrs = @{@"code":codeString};
__block NSArray *responseResult = @[];
VKRequest *request = [VKApi requestWithMethod:@"execute"
andParameters:parametrs
andHttpMethod:@"GET"];
request.waitUntilDone = YES;
[request executeWithResultBlock:^(VKResponse *response) {
NSArray *tempArray = response.json;
if ([tempArray count] < 1) {
responseResult = @[];
} else {
responseResult = tempArray;
}
} errorBlock:^(NSError *error) {
NSLog(@"ERROR LOADING COMMENTS %@", error.localizedDescription);
}];
return responseResult;
} // executeCommentsForPosts
В данных примерах, ничего выдающегося не произошло. Все методы легко покрываются возможностями официального SDK ВК, нужно лишь внимательно изучить его возможности.
Собственно, о “костылях”
Ну а как без костылей? Запланировав в обновлении метод чистки лайков на стене, не составило труда сделать выборку всех отметок “нравится”, где по логике обработчика нужно было добавить пользователя в черный список, после чего его отметки должны исчезнуть из всех постов. Но я поймал настойщий ступор после того, как обнаружил, что метод “account.banUser” не решает проблемы удаления лайка. Все отметки оставались на своих местах. Обратившись с вопросом к сообществу разработчиков ВК, я получил отсылки только к данному методу. Поиск в google & stackoverflow также не принес результатов. Встречались лишь упоминания о том, что данная операция ранее успешно применялась в программе VKBot, но она не работоспособна на настоящий момент и проверить так ли это, я не смог.
Дело прошлое, я уже решил отказаться от реализации метода, но решил послушать браузерный трафик при ручном удалении лайка.
Для исследования использовалась программа из Mac App Store — “Cellist”, выступающая в качестве прокси, через которую можно прогонять трафик для исследований. Настроив программу на ноутбуке, я получил ssl-сертификат для своего телефона и настроил устройство так, чтобы в сеть оно ходило через этот прокси на моем компьютере. Далее просто была авторизация на своей странице, случайный выбор поста и ручное удаление лайка.
Вот в этот момент и заметил, что при ручном удалении, запрашивается hash для объекта, который после используется при подтверждении своего намерения об удалении.
Ну а далее все просто. Осталось лишь в коде воссоздать последовательность операций, что действие как будто происходит в браузере.
Вот мой пример решения. Возможно он не слишком удачен и его можно публиковать на govnokod.ru, но с поставленной задачей он справляется.
- (id)initWithUserID:(NSString *)userID
andObjID:(NSString *)objectID {
if (self = [super init])
stringUserID = userID;
stringObjectID = objectID;
return self;
} // initWithID
- (void)main {
if (self.isCancelled) {
return;
}
[self prepareHashWithUserID:stringUserID
andObjectID:stringObjectID];
} // main
- (void)prepareHashWithUserID:(NSString *)userID
andObjectID:(NSString *)objID {
NSString *wallUserID = [[NSUserDefaults standardUserDefaults] stringForKey:@"wallUserID"];
NSString *objectWall = [NSString stringWithFormat:@"wall%@_%@", wallUserID, objID];
NSString *urlString = [NSString stringWithFormat:@"http://ift.tt/1TatAww", userID, objectWall];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSArray *array = [dataString componentsSeparatedByString:@"hash: '"];
NSString *hashString = array[1];
array = [hashString componentsSeparatedByString:@"'"];
hashString = array[0];
[self deleteLike:userID
andHash:hashString
andObjID:objectWall];
} // prepareHashWithUserID
- (BOOL)deleteLike:(NSString *)userID
andHash:(NSString *)hash
andObjID:(NSString *)objID {
NSString *urlString = [NSString stringWithFormat:@"http://ift.tt/1PfOcp4", hash, userID, objID];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
NSHTTPURLResponse *response = nil;
NSError *error = nil;
[NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
BOOL result;
if (!error) {
result = YES;
} else {
result = NO;
}
return result;
} // deleteLike
Изменилось ли что-нибудь после обновления?
Нет, не изменилось! Количество установок держится на прежнем уровне. Приложение было переработано полностью и переписано с нуля по всем канонам разработки. Новый дизайн, контроллеры детальных представлений, дополнительный функционал, “костыль” в коде, но конверсию это не увеличило!
В приложении использована статистика Яндекс для мобильных приложений в реальном времени. При совершении покупки, на успешном выходе из метода, ко мне летит сообщение о факте покупки пользователем того или иного расширения. Если судить по Яндекс метрике, то я богат, но если обратить внимание чуть далее на количество “рутованных” устройств, то становится понятно, что богатство мое виртуально, как в старом бородатом анекдоте про разговор отца с сыном!
“Виртуально сынок мы миллионеры, но на деле имеем лишь 2-х … и 1-го сына …."
Посмотреть то, что получилось, можно здесь: App Store
Конечно, есть недоработки в коде, их предстоит устранять, но не знаю стоит ли овчинка выделки!
P.S.: Хотелось бы послушать разработчиков, как они повышают конверсию. Если кому есть что рассказать, то прошу писать в комментах или личных сообщениях.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий