GraphQL vs REST в Swift: что выбрать iOS-разработчику в 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: REST vs GraphQL — старая битва в новом мире Swift

Когда-то давно, в эпоху Objective-C и диспетчеризации по селекторам, выбор между REST и GraphQL не стоял так остро. REST был королём, а JSON — его верным оруженосцем. Но времена изменились. Swift стал языком с сильной системой типов, появились async/await, Codable и мощные фреймворки вроде Apollo iOS. Теперь вопрос «REST или GraphQL?» звучит иначе: «Что даст моему Swift-проекту больше профита?».

В этой статье мы не будем пересказывать википедию. Мы возьмём реальный пример iOS-приложения для образовательной платформы (да, той самой PythonLib) и посмотрим, как каждый подход ведёт себя в коде на Swift. Вы узнаете, когда REST всё ещё рулит, а когда GraphQL — настоящий спаситель.

Коротко о главном: чем REST отличается от GraphQL

Для тех, кто только вкатывается в тему — базовые понятия.

  • REST — это архитектурный стиль, где каждый ресурс (пользователь, курс, урок) имеет свой URL. Вы получаете фиксированную структуру данных, которую сервер решил вам отдать. Часто — целую пачку лишнего.
  • GraphQL — это язык запросов. Вы сами решаете, какие поля нужны. Одна точка входа (/graphql) и один запрос может заменить несколько REST-запросов.

На бумаге GraphQL выглядит как серебряная пуля. Но в Swift-проектах есть нюансы, которые мы сейчас разберём на практике.

Пример 1: Получаем профиль пользователя и его курсы

REST-подход на Swift (URLSession + Codable)

Допустим, у нас есть классический REST API. Чтобы показать профиль и список курсов, нужно два запроса:

// Модели
struct UserProfile: Codable {
    let id: Int
    let name: String
    let email: String
    let avatarURL: String
}

struct Course: Codable {
    let id: Int
    let title: String
    let progress: Double
}

// Сетевой слой
func fetchUserProfile(userId: Int) async throws -> UserProfile {
    let url = URL(string: "https://api.pythonlib.ru/users/\(userId)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(UserProfile.self, from: data)
}

func fetchCourses(userId: Int) async throws -> [Course] {
    let url = URL(string: "https://api.pythonlib.ru/users/\(userId)/courses")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode([Course].self, from: data)
}

// Использование
let profile = try await fetchUserProfile(userId: 42)
let courses = try await fetchCourses(userId: 42)

Всё просто и предсказуемо. Но есть проблема: мы получаем все поля профиля, даже если нам нужны только имя и email. А ещё — два сетевых запроса. На медленном соединении это боль.

GraphQL-подход на Swift (Apollo iOS)

Теперь то же самое, но через GraphQL. Сначала определяем запрос в файле .graphql:

query GetUserWithCourses($userId: ID!) {
    user(id: $userId) {
        id
        name
        email
        courses {
            id
            title
            progress
        }
    }
}

Apollo генерирует типизированный Swift-код автоматически. В Xcode это выглядит так:

import Apollo

let apolloClient = ApolloClient(url: URL(string: "https://api.pythonlib.ru/graphql")!)

func fetchUserWithCourses(userId: Int) async throws -> GetUserWithCoursesQuery.Data {
    return try await withCheckedThrowingContinuation { continuation in
        apolloClient.fetch(query: GetUserWithCoursesQuery(userId: userId)) { result in
            switch result {
            case .success(let graphQLResult):
                if let data = graphQLResult.data {
                    continuation.resume(returning: data)
                } else if let errors = graphQLResult.errors {
                    continuation.resume(throwing: errors.first!)
                }
            case .failure(let error):
                continuation.resume(throwing: error)
            }
        }
    }
}

// Использование
let data = try await fetchUserWithCourses(userId: 42)
let userName = data.user?.name
let courseTitles = data.user?.courses?.compactMap { $0?.title }

Видите разницу? Один запрос — и мы получили ровно то, что просили. Никаких лишних полей. Для мобильного разработчика это означает меньше трафика и более быстрый рендеринг экрана.

Типизация: Codable vs сгенерированные модели

В REST мы пишем модели руками. Это гибко, но есть риск ошибиться в названии поля или типе данных. Компилятор вас не спасёт, если сервер вернёт name как строку, а вы ожидаете Int.

GraphQL в связке с Apollo даёт строгую типизацию на уровне генерации кода. Если бэкенд изменит тип поля, ваш проект просто не соберётся. Для больших команд это must-have. Для маленьких проектов — иногда избыточный оверхед.

Совет PythonLib: Если вы пишете pet-проект или MVP, REST + Codable — ваш выбор. Быстро, дешево, сердито. Если же у вас команда из 5+ iOS-разработчиков и сложная бизнес-логика — GraphQL сэкономит вам часы на синхронизации.

Производительность: когда GraphQL может навредить

Звучит парадоксально, но GraphQL не всегда быстрее. Рассмотрим сценарий: у вас есть список из 100 курсов. В REST вы делаете один запрос к /courses и получаете всё разом. Сервер может закэшировать этот ответ на CDN.

В GraphQL вы тоже делаете один запрос, но сервер каждый раз вычисляет ответ динамически. Если в запросе есть вложенные связи (например, для каждого курса подгружать автора и отзывы), сложность запроса растёт. Без грамотной настройки batch loading и DataLoader GraphQL может породить N+1 проблему.

Для Swift-разработчика это значит: если ваш бэкенд не оптимизирован под GraphQL, вы рискуете получить ответ дольше, чем при REST. Всегда тестируйте реальные сценарии.

Кэширование: REST проще, GraphQL сложнее

В REST кэширование на уровне HTTP — это стандарт. ETag, Cache-Control, Last-Modified — всё работает из коробки. Вы можете использовать URLCache в Swift и получить кэш бесплатно.

let config = URLSessionConfiguration.default
config.urlCache = URLCache(memoryCapacity: 50_000_000, diskCapacity: 100_000_000)
let session = URLSession(configuration: config)

С GraphQL всё сложнее. Запросы — это POST (обычно), и HTTP-кэш не работает. Приходится использовать нормализованный кэш на клиенте. Apollo iOS предоставляет встроенный кэш, но его нужно правильно настроить:

let cache = NormalizedCache()
let store = ApolloStore(cache: cache)
let client = ApolloClient(networkTransport:                

Блоги

Рекомендаций по книгам