Issue #360

Given a streaming service

service Server {
  rpc GetUsers(GetUsersRequest) returns (stream GetUsersResponse);
}

To get a response list in Swift, we need to do observe stream, which is a subclass of ClientCallServerStreaming

func getUsers(roomId: String, completion: @escaping (Result<[User], Error>) -> Void) {
    let request = withValue(Server_GetUsersRequest()) {
        $0.roomId = roomId
    }

    DispatchQueue.global().async {
        var users = [User]()

        do {
            var streaming = true
            let stream = try self.client.getUsers(request, completion: { _ in
                streaming = false
            })

            while streaming {
                if let response = try stream.receive() {
                    users.append(response.user)
                }
            }

            DispatchQueue.main.async {
                completion(.success(users))
            }
        } catch {
            DispatchQueue.main.async {
                completion(.failure(error))
            }
        }
    }
}

This can get repetitive very fast. To avoid the duplication, we can make a generic function

import SwiftGRPC

func getStream<Streaming, Response>(
    makeStream: @escaping (@escaping () -> Void) throws -> Streaming,
    receive: @escaping (Streaming) throws -> Response?,
    completion: @escaping (Result<[Response], Error>) -> Void) {

    DispatchQueue.global().async {
        var responses = [Response]()

        do {
            var streaming = true

            let stream = try makeStream({
                streaming = false
            })

            while streaming {
                if let response = try receive(stream) {
                    responses.append(response)
                }
            }

            DispatchQueue.main.async {
                completion(.success(responses))
            }
        } catch {
            DispatchQueue.main.async {
                completion(.failure(error))
            }
        }
    }
}

Since swift-grpc generates very concrete structs, we need to use generic. The difference is the Streaming class and Response struct

func getUsers(roomId: String, completion: @escaping (Result<[User], Error>) -> Void) {
    let request = withValue(Server_GetUsersRequest()) {
        $0.roomId = roomId
    }

    getStream(
        makeStream: { completion in
            return try self.client.getUsers(request, completion: { _ in
                completion()
            })
        }, receive: { stream in
            return try stream.receive()
        }, completion: { result in
            completion(result.map { $0.map { $0.user }})
        })
}

Handle CallResult

import SwiftGRPC
import SwiftProtobuf

extension CallResult {
    func toError() -> NSError {
        return NSError(domain: "com.myApp", code: statusCode.rawValue, userInfo: [
            "status_code": statusCode,
            "status_message": statusMessage ?? ""
        ])
    }
}