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 ?? ""
])
}
}