ESPN DeportesIsaac del Toro, quinto lugar del Mundial de CiclismoThe Jerusalem PostSmotrich, Strock demand Netanyahu reject new BoP Gaza plan, say it contradicts US-Israel agreementsESPNFlorida jumps to highest ranking since 2020 as Texas solidifies grip on No. 1Daily MaverickBill Gates joins calls for AI safeguards, including legislationRTP DesportoGonçalo Ramos marca o 2-1InquirerDENR calls for cleanup of disturbed areas at Sibuyan minen-tvDFB-Elf patzt in Nations League: Griechenland verdirbt Jürgen Klopp die HeimpremiereBillboardStella Lefty Parts Ways With Managementالشرقتصعيد جديد.. إسرائيل تنهي حصانة الدبلوماسيين الهولنديين في رام اللهANSA SportFigc: botta e risposta Ceferin-Buonfiglio, e arriva sponsor ItalPetroliХабрВсе (я надеюсь) способы работать с транзакциями в goDigital SpyLord of the Rings star announced for surprising new project
The Daily Newsstand · Free, Always
Sunday, September 27, 2026

Применение gRPC при разработке приложений под iOS

Translate

Всем привет! При широком распространении REST подхода для клиент-серверного взаимодействия всегда существовали и существуют альтернативные решения, иногда незаслуженно обделенные вниманием. Об одном из них речь пойдет в сегодняшней статье. В ней будут даны некоторые теоретические основы, необходимые для работы и создан небольшой проект, который можно использовать для перехода на gRPC.

Что такое gRPC?

Чтобы начать разбираться в основной теме материала необходимо повторить/изучить то, что подробно рассматриваться не будет, а именно - REST. Хорошее объяснение того, что это такое можно найти в этой статье. Если сформулировать краткую выдержку, то получится, что REST (Representational State Transfer / Передача состояния представления) - это архитектурный стиль для проектирования распределённых приложений, который определяет набор правил для взаимодействия компонентов (клиента и сервера) через интернет, используя протокол HTTP и его методы (GET, POST, PUT, DELETE) для работы с ресурсами (данными). Ресурсы идентифицируются некоторым URL, а формат данных для передачи чаще всего представлен JSON-ом. REST действительно очень популярен в нынешнее время, и мобильная разработка тут исключением не является.

Однако при всех имеющихся плюсах, недостатки тоже имеют место быть:

  • Наличие "лишних" данных при передаче - часто бывает так, что с выходом новой версии приложения существующие запросы дорабатываются, какие-то поля из ответов становятся больше не нужны, однако нельзя забывать о том, что старые версии все еще продолжают с ними работать, и поэтому убрать их нельзя на стороне сервера, что в итоге приводит к избытку данных и дополнительному трафику

  • Использование текстового а не бинарного формата данных (возможность обмениваться бинарной информацией тоже есть, но не распространена) - тоже создает нагрузку для трафика, хоть и удобен для отладки запросов через программы-снифферы

  • Избыток запросов - со временем и в следствие неверного построения API, количество дублирующих/очень похожих друг на друга запросов/методов растет, к тому же сложно применять подобный подход в системах, где требуется постоянная передача данных, к примеру мессенджеры

Все вышеперечисленные моменты привели к появлению других решений. Одно из таких - RPC. Это в некотором роде концепция, объединяющая под собой разные имплементации, однако и про нее стоит почитать, например в этом материале. Если вкратце, то RPC (Remote Procedure Call / Удалённый вызов процедур) — это технология в программировании, которая позволяет программе (клиенту) вызывать функции или процедуры на другом компьютере (сервере) в сети так, будто они находятся локально, скрывая сложность сетевого взаимодействия от разработчика. Часто этот подход используют при создании распределенных систем.

Так вот gRPC и является одним из реализаций RPC-подхода. gRPC (Google Remote Procedure Calling) - фреймворк с открытым исходным кодом от Google для удалённых вызовов процедур, использующий HTTP/2 в качестве транспорта и Protocol Buffers для сериализации данных, что делает его независимым от языка программирования. В этом и заключается одно из его преимуществ. Для теоретического изучения его основ лучше обратиться к имеющимся материалам, так как данная статья более практическая и всю теорию не охватит.

Настраиваем все необходимое

Для работы потребуется установить некоторое ПО. Предполагается, что компьютер находится под управлением MacOS, среда разработки приложения - Xcode, в общем все максимально нативное с точки зрения Apple.

Пишем сервер

Для тестирования сгодится локальный "сервер", работающий на Вашем компьютере, поэтому следует его написать. Это можно сделать на нескольких языках программирования, что поддерживают grpc, тут в примере будет сделан сервер на Swift, но отталкиваться нужно от того, какая реализация будет удобнее.
Для создания iOS-приложения (и серверной части) следует предварительно поставить нужные инструменты. Через Homebrew надо установить protobuf.

brew install protobufswift-protobuf protoc-gen-grpc-swift

Создайте папку проекта и выполните следующую команду
swift package init --type executable

В SPM добавить зависимость от grpc-swift. Наш целевой вид package-а может выглядеть так (есть корреляции в зависимости от версии ОС и grpc, какие решено было использовать)

let package = Package(
    name: "GrpcServer",
    platforms: [.iOS(.v13), .macOS(.v12)],
    dependencies: [
        .package(url: "https://github.com/grpc/grpc-swift.git", from: "1.24.1"),
        .package(url: "https://github.com/apple/swift-protobuf.git", from: "1.29.0"),
    ],
    targets: [
        .executableTarget(
            name: "GrpcServer",
            dependencies: [
                .product(name: "GRPC", package: "grpc-swift"),
                .product(name: "SwiftProtobuf", package: "swift-protobuf"),
            ]
        )
    ]
)

Контракт между сервером и клиентом описывается в файле формата .proto. Его необходимо так же разместить для удобства в директории проекта. Содержимое выглядит следующим образом:

syntax = "proto3";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

После чего требуется запустить команду (пути заменить на локальные):

 protoc \              
  --plugin=protoc-gen-grpc-swift=$(pwd)/.build/debug/protoc-gen-grpc-swift \
  --swift_out=. \
  --grpc-swift_out=Sources \
  --proto_path=Sources \
  Sources/greeter.proto

Обратите внимание, что сгенерированные файлы должны лежать вместе с основным кодом - main.swift.

import GRPC
import NIO
import Foundation

final class GreeterProvider1: GreeterAsyncProvider {
    func sayHello(
        request: HelloRequest,
        context: GRPCAsyncServerCallContext
    ) async throws -> HelloReply {
        let recipient = request.name.isEmpty ? "stranger" : request.name
        return HelloReply.with {
            $0.message = "Hello \(recipient)!"
        }
    }
}

let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount)
defer {
    try! group.syncShutdownGracefully()
}

let provider = GreeterProvider1()

let server = Server.insecure(group: group)
    .withServiceProviders([provider])
    .bind(host: "127.0.0.1", port: 50051)

server.map {
    $0.channel.localAddress
}.whenSuccess { address in
    print("Сервер запущен на \(address!)")
}

_ = try server.flatMap {
    $0.onClose
}.wait()

Запустить код можно, вызвав
swift run GrpcServer

При возникновении проблем, можно обратиться к следующим командам для сброса результатов и очистки:

swift package clean
rm -rf .build
rm -f Package.resolved

Пишем клиент

Создадим iOS-клиент для тестирования взаимодействия. В SPM поставим зависимости GRPC версии, совместимой с серверной, и SwiftProtobuf. Сгенерированные файлы (pb.swift и grpc.swift) для серверной части необходимо добавить в bundle проекта.
Зависимости могут выглядеть следующим образом:

Зависимости iOS приложения

Зависимости iOS приложения

Код нашего network-сервиса выглядит так:

import Foundation
import GRPC
import NIO

final class GreeterClientService {
    private let group: EventLoopGroup
    private let connection: ClientConnection
    private let client: GreeterClientProtocol
    private let channel: GRPCChannel
    
    init(host: String = "127.0.0.1", port: Int = 50051) {
        self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)

        let configuration = ClientConnection.Configuration(
            target: .hostAndPort(host, port),
            eventLoopGroup: group
        )

        self.connection = ClientConnection(configuration: configuration)
        
        self.channel = try! GRPCChannelPool.with(
            target: .host(host, port: port),
            transportSecurity: .plaintext,
            eventLoopGroup: group
        )

        self.client = GreeterClient(channel: channel)
    }

    func sayHello(name: String) throws -> String {
        var request = HelloRequest()
        request.name = name

        let response = try client.sayHello(request).response.wait()
        return response.message
    }

    func shutdown() {
        try? connection.close().wait()
        try? group.syncShutdownGracefully()
    }
}

Код ContentView, где происходит обращение к сервису и получение результата:

struct ContentView: View {
    @State private var name: String = ""
    @State private var reply: String = ""
    @State private var isLoading: Bool = false
    @State private var errorMessage: String?
    
    private let client = GreeterClientService(
        host: "127.0.0.1",
        port: 50051
    )
    
    var body: some View {
        VStack(spacing: 20) {
            TextField("Введите имя", text: $name)
                .textFieldStyle(.roundedBorder)
                .padding(.horizontal)
            
            Button("Say Hello") {
                callServer()
            }
            .disabled(name.isEmpty || isLoading)
            .buttonStyle(.borderedProminent)
            
            if isLoading {
                ProgressView()
            }
            
            if let error = errorMessage {
                Text("Ошибка: \(error)")
                    .foregroundColor(.red)
            }
            
            if !reply.isEmpty {
                Text(reply)
                    .font(.title2)
                    .padding()
            }
        }
        .padding()
        .onDisappear {
            client.shutdown()
        }
    }
    
    private func callServer() {
        isLoading = true
        errorMessage = nil

        DispatchQueue.global(qos: .userInitiated).async {
            do {
                let result = try client.sayHello(name: name)
                DispatchQueue.main.async {
                    reply = result
                    isLoading = false
                }
            } catch {
                DispatchQueue.main.async {
                    errorMessage = error.localizedDescription
                    isLoading = false
                }
            }
        }
    }
}

В случае успешного запуска приложения при запущенном сервере все будет работать.
В следующих статьях разберу подробнее, что генерируется и как работает все взаимодействие "под капотом".

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.