Добрый день, хаброжители,
Статьи посвящены тому, как я справляюсь с поддержкой приложений, которые прошли не через одну версии, писались в разное время и разными людьми. Надеюсь, они помогут и другим iOS разработчикам.
- Облегчаем поддержку iOS приложения. Часть 1 — не отрываясь от Xcode
- Облегчаем поддержку iOS приложения. Часть 2 — локация и сеть
- Облегчаем поддержку iOS приложения. Часть 3 — падение и логи
В первой статье я поделился своим опытом работы с трудно воспроизводимыми багами. В этой статье я расскажу, как можно поступить с багами, которые связаны с сетью или локацией. Тех, кого интересует эта тема, прошу под кат.
Внимательный читатель первой статьи мог догадаться, что долгое время я проработал в аутсорсе(не надо ставить на мне крест). И поэтому мои статьи описывают ситуации, где есть заказчик, который далеко от вас.
Подвид второй — невероятные условия локации
«Вот у меня тут, в Париже, данные не так отображаются»
Бывает, что многое в приложении завязано на локацию, и у вас все отлично, а вот у клиента данные не так отображаются и он в Париже… ну хорошо ему, что еще могу сказать. А я в Сибири, и у меня все ОК с данными, каждому свое. Что же делать? Симулировать локацию. И так, рассмотрим наши варианты:
1) Это простая подмена локации в симуляторе. Как нечего делать — он поставит вас в нужную точку. «iOS Simulator»→Debug→Location.
2) Если точки мало, можно начать симулировать путь, есть файлы pgx, добавляются в xcode через правый значок
И локации будут перемещать вас по пути, но… Если мне не изменяет память, это можно делать, только присоединив приложение к Xcode, и нельзя настроить время между прыжками. Что делать тестировщикам? Сидеть у маков? Хорошая компания, если всем купили MAC. И всегда подключив приложение Xcode? А если нужны задержки большие, больше, чем сделает Xcode?
3) Вот тут на помощь неожиданно приходят волшебные методы runtime, о них много написано, к примеру тут, но вот реально зачем оно такое может пригодиться… бывает не совсем понятно. Однако, вот вам вариант, который поможет нам решить проблему с подменой локации или даже навигации. FakeGPSUtility строго не судить, несколько раз он пригодился и сыграл важную роль, но это тот еще велосипед, и тут он больше чтобы не быть голословным и показать вам направление. Свою цель этот проект выполняет — вы можете подменять локацию и прыжки межу ними не фиксированы. Это может делать кто угодно, даже клиент, не нужен Xcode и главное для меня (для кого-то вторичное) мы не меняем ничего в коде проекта, все подменяется в момент запуска приложения. То есть, вы присоединяете его к проекту, код проекта остается прежний, фиксаете баг/тестируете и удаляете, или выставляете нужные #define. Вуаля, полезная тулза под рукой, и никогда этот код не попадет в Release, исключительно для тестов.
С iOS 8, карты начали получать локацию (реальную локацию девайса), даже при симуляции, так что если дойдут руки — допилю тулзу.
А пока она может вот такое сделать с картой. Красная булавка это то, что прилетает в методе делегата NSLocationManager, остальное — нативное поведение карты. Симуляция шла — карта велась.
Подвид третий — проблемы с http request
— При edge соединении приложение плохо работает
— Именно поэтому я и тестирую его на WiFi, зачем же себя мучить-то!
Рассмотрим еще один случай — проблема проявилась из-за плохой сети. Вообще, полезно просто симулировать плохое соединение с интернетом и посмотреть, как работает приложение, потому что, пока вы на WiFi, а может и сервис на localhost, не поймешь как оно ведет себя на 3g (а, не дай Бог, еще и на edge). Так что, ради интереса, идем на устройстве в Settings→DeveloperNetwork Link Conditioner→Enable и выбираем, насколько плохое соединение мы хотим. После этого вы можете резко поменять точку зрения о быстродействии приложения.
И так, вы можете сделать интернет медленнее, но что если вам надо сломать какой-то один определенный response, чтобы получилась та же проблема, что и у клиента/тестировщика? Тогда придется делать еще один велосипед (дзынь-дзынь).
Как и с FakeGPSUtility, я против допиливать что-то внутри наших рабочих классов для тестирования и баговоспроизведения, код добавляется исключительно вокруг проекта. NSURProtocol нам в помощь. Как по мне, 3 наиболее вероятных места, где может понадобится сломать request — это NSURLSessionTask, UIWebView и WKWebView. Есть и другие соединения по сети, но тут я вам не помогу — ничего хорошего не посоветую, только сидеть и портить код проекта ради тестов.
NSURLSessionTask
Чтобы сломать его response, надо, чтобы наш протокол попал во все NSURLSessionConfiguration, для этого переопределим
- (NSArray *)protocolClasses
И просто возвращаем массив с нашим MyURLProtocol. Если ваше приложение использует иные протоколы — добавьте их тоже (чтобы не тащить хидеры, можно использовать NSClassFromString). Если же у вас еще более сложная логика и протоколы не везде и не всегда используются, то… сами виноваты — допиливайте велосипед по своему образу и подобию.
static char kAssociatedObjectKey; - (NSArray *)protocolClasses { NSMutableArray *result = objc_getAssociatedObject(self, &kAssociatedObjectKey); if (result == nil) { result = [self customeProtocolsArray]; objc_setAssociatedObject(self, &kAssociatedObjectKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return result; } - (void)setProtocolClasses:(NSArray *)protocolClasses { NSMutableArray *result = [self customeProtocolsArray]; if (protocolClasses.count > 0) { [result addObjectsFromArray:protocolClasses]; } objc_setAssociatedObject(self, &kAssociatedObjectKey, result, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSMutableArray *)customeProtocolsArray { return [NSMutableArray arrayWithObject:[MyURLProtocol class]]; }
Но, для bacgkound session это не сработает. пруф
UIWebView
Тут все еще проще — просто зарегистрируйте класс. Например так:
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [NSURLProtocol registerClass:[MyURLProtocol class]]; return YES; }
WKWebView
А вот тут все плохо, не получится подменять извне, придется все-таки втыкать костыли для этого в код проекта.
@implementation MyURLProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { #if !defined(STAGE_SERVER) || !STAGE_SERVER return NO; #endif NSString *urlString = [[request URL] absoluteString]; NSRange randge = [urlString rangeOfString:@"http://yandex.ru"]; if (randge.location == 0) { return YES; } return NO; } + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return NO; } - (void)startLoading { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSError *error = [NSError errorWithDomain:@"MyErrorDomain" code:42 userInfo:@{ NSLocalizedDescriptionKey: @"We fail it!"}]; [self.client URLProtocol:self didFailWithError:error]; }); } - (void)stopLoading { // stop request if you can } @end
Подвид четвертый — для тестов нужен специфический/устаревший response
Нам сообщают, что вчера было воспроизведено некорректное поведение, а сегодня уже не получается, от сервиса не выходит получить тот же ответ. Сказать, что теперь все работает, неправильно, так как по закону жанра эта ситуация повторится в первый же день релиза. И повлиять на server-side team вы не можете по целому ряду причин: они в другом городе, вы их не знаете, они в отпуске.
В этой ситуации NSURLProtocol снова спешит вам на помощь. Нам надо лишь поменять
Вот так может выглядеть наш MyURLProtocol, чтобы просимулировать нужный response
@implementation MyURLProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { NSString *urlString = [[request URL] absoluteString]; NSRange randge = [urlString rangeOfString:@"http://localhost:1984/oldresponse"]; if (randge.location == 0) { return YES; } return NO; } + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return NO; } - (void)startLoading { NSString *path = [[NSBundle mainBundle] pathForResource:@"unexistd_response" ofType:@"json"]; NSData *data = [NSData dataWithContentsOfFile:path]; [self.client URLProtocol:self didLoadData:data]; [self.client URLProtocol:self didFailWithError:nil]; } - (void)stopLoading { // stop request if you can } @end
Тут мы с файловой системы читаем unexistd_response.json, но никто не запрещает сделать редирект на рабочую машину, поднять на ней небольшой сервис и с него получить ответ. Это отличный вариант, если есть время и не хочется складывать странные файлы в проект, даже если они только на время и для тестов.
Лично я часто создаю NSURLProtocol для работы на первых порах над проектом, потому что зачастую сервис есть только в планах и ТЗ, а реально у вас его нет. Но уже хочется писать, как будто приложение работает как надо, получает данные, и, в зависимости от них, показывает содержимое пользователю. Я делаю себе
Заключение
Сегодня мы рассмотрели случаи, когда проблемы очень тесно связаны с http запросами или локациями. NSURLProtocol и FakeGPSUtility герои сегодняшней статьи, они очень сильно упростят тестирование и воспроизведение некорректного поведения приложения.
ссылка на оригинал статьи http://habrahabr.ru/post/254891/
Добавить комментарий