Оптимізація мережевих запитів мобільної програми
Головний екран програми робит 14 паралельних запитів при відкриття. Здавалось би — паралельно, значит швидко. Але HTTP/1.1 ограничен 6 з'єднаннями до одного хосту, та 8 запитів стоять у черзі. На слабому LTE з RTT 180 мс — суммарне очікування до готовності екрану перевищує 2 секунди. Перехід на HTTP/2 з мультиплексуванням або агрегація запитів на BFF-слої (Backend for Frontend) розв'язує це без змін у клієнтському коді.
Де втрачається час
Лишні запити. Найчастіше — відсутність кешування на клієнті. URLSession на iOS за замовчуванням поважає Cache-Control заголовки, але лише якщо сервер їх виставляє. Якщо API повертає Cache-Control: no-store «для надійності» — кожне звращення до довідкових даних (категорії, налаштування, конфігурація) йде по мережі. URLCache з ліміт 50 MB та ручна установка URLRequest.cachePolicy = .returnCacheDataElseLoad для read-only endpoint-ів працює як швидкий патч.
Надлишкові payload. REST-endpoint для списку користувачів повертає 40 полів, з яких UI використовує 4. На списку з 100 елементів це лишні 60–80 KB JSON на кожний запит. GraphQL розв'язує це на рівні протоколу, але якщо GraphQL немає — ?fields=id,name,avatar_url як query-параметр фільтрації хоча б частково рятує ситуацію.
Повторні запити при ротації екрану. На Android ViewModel + LiveData / StateFlow тримають результат запиту та не перезапускають його при пересоздані Activity. Але якщо запит живе у Fragment.onViewCreated без перевірки — при кожній ротації йде новий мережевий виклик. Діагностується через Charles Proxy або OkHttp EventListener з логуванням.
Інструменти та рішення
iOS (URLSession / Alamofire / Moya):
Alamofire RequestInterceptor — зручне місце для retry-логіки з exponential backoff:
func retry(_ request: Request, for session: Session, dueTo error: Error,
completion: @escaping (RetryResult) -> Void) {
let delay = min(pow(2.0, Double(request.retryCount)), 30.0)
completion(.retryWithDelay(delay))
}
URLSession з waitsForConnectivity = true — запит автоматично чекає відновлення сети замість негайної помилки. Критично для offline-first програм.
Android (OkHttp / Retrofit):
OkHttp CacheInterceptor уже вбудований, достатньо передати Cache при створенні клієнта:
val cache = Cache(context.cacheDir, 50L * 1024 * 1024)
val client = OkHttpClient.Builder().cache(cache).build()
Retrofit + suspend fun — автоматична відмена запиту при смерті coroutine scope. Головне — привязувати scope до viewModelScope, а не до GlobalScope.
Дедупліація запитів. Якщо кілька компонентів одночасно запрошують один ресурс — виконувати запит один раз. На iOS — Combine з share() оператором на Publisher. На Android — StateFlow у Repository: перший підписчик запускає запит, решта отримує результат із того ж flow.
Request prioritization
На iOS URLSession підтримує URLRequest.networkServiceType: .responsiveData для дій користувача, .background для аналітики та prefetch. Система пріоритизує трафік відповідно — аналітика не конкурує за bandwidth з запитом користувача.
На Android WorkManager з NetworkType.CONNECTED та пріоритетом EXPEDITED vs стандартним — для фонової синхронізації даних без блокування основного потоку запитів.
Кейс: GraphQL N+1 на мобілі
Програма використовувала GraphQL, але запити будувалися «як зручно» — окремий query на кожну карточку у списку при детальному перегляді. 20 карточок = 20 запитів. Внедрення DataLoader-паттерну на клієнті через @defer directive (Apollo iOS / Apollo Android підтримують) дозволило батчити запити. Час завантаження детального екрану — з 2.8 с до 0.6 с.
Строки
Аудит мережевого шару та точкові оптимізації — 3–5 днів. Впровадження кешування, retry-логіки та дедупліації по всій програмі — 1–2 тижні.







