grpc-swiftΛͬͯiOSΞϓϦͰշదͳgRPC௨৴Λߦ͏ɹɹ2018/08/31 iOSDC
View Slide
About Meҏ౻ɹګฏGithub : KyoheiG3Twitter : @KyoheiG3
About MeΩϡϨʔγϣϯAmeba OWNDAbemaTVSUPERCHOICEࠓ·ͨ৽ن
gPRC
gRPCͬͯԿʁ• Google͕࡞ͬͨRPCΛ࣮ݱ͢ΔͨΊͷϑϨʔϜϫʔΫ• ͚ࣾͷStubbyΛɺ2015ʹΦʔϓϯԽͨ͠ͷ• HTTP2
RPCͬͯԿʁ• ϦϞʔτϓϩγʔδϟίʔϧͷུ• ϓϩάϥϜ͔ΒผͷΞυϨεۭؒʹ͋Δαϒϧʔνϯखଓ͖Λ࣮ߦ͢Δ͜ͱΛՄೳʹ͢Δٕज़
HTTP2• SPDYΛج൫ʹ࡞Γग़͞Εͨ• SPDYGoogle
Մೳͳ௨৴ํࣜ
Unaryʢ୯ൃͷ௨৴ʣ• 1ͭͷϦΫΤετʹର͠ɺϨεϙϯεΛ1ͭฦ͢• HTTP1ܥͱಉ͡Α͏ͳεςʔτϨεͳ௨৴ํࣜ
Server StreamingʢαʔόετϦʔϛϯάʣ• 1ͭͷϦΫΤετʹର͠ɺϨεϙϯεΛෳฦ͢• αʔόpush ϙʔϦϯάʹஔ͖ΘΔ
Client StreamingʢΫϥΠΞϯτετϦʔϛϯάʣ• ෳͷϦΫΤετʹର͠ɺϨεϙϯεΛ1ͭฦ͢• ϑΝΠϧΞοϓϩʔυͳͲ
Bi-Directional StreamingʢํετϦʔϛϯάʣ• ෳͷϦΫΤετʹର͠ɺϨεϙϯεΛෳฦ͢• νϟοτͳͲ
Αࣖ͘ʹ͢ΔProtocol Buffersͱͷҧ͍ͬͯʁ• Protocol Buffers௨৴͢Δσʔλͷܗࣜʢjson, xml...ʣ• gRPC௨৴ͷखஈ
͢͜͠Protocol Buffersͷ͓͞Β͍
protobuf• protobufͱུ͞ΕΔ͜ͱ͕ଟ͍• όΠφϦϕʔε• .protoϑΝΠϧͰσʔλܗࣜΛఆٛ
echo.protosyntax = "proto3";package echo;message EchoRequest {string text = 1;}message EchoResponse {string text = 1;}
protobuf• .protoϑΝΠϧ͔ΒɺswiftgoϑΝΠϧΛ࡞Մೳ
protobuf$ protoc echo.proto --plugin=./protoc-gen-swift --swift_out=.
echo.pb.swiftstruct Echo_EchoRequest {var text: String = String()var unknownFields = SwiftProtobuf.UnknownStorage()init() {}}struct Echo_EchoResponse {var text: String = String()var unknownFields = SwiftProtobuf.UnknownStorage()init() {}}
protobuf• swiftͰApple͕ެࣜʹαϙʔτ1• όΠφϦͷγϦΞϥΠζɾσγϦΞϥΠζͷϥΠϒϥϦʹґଘ• ܰͯ͘ܕ҆શ1 https://github.com/apple/swift-protobuf
protobuf σϝϦοτ• ௨৴σʔλͷՄಡੑ͕͗͢Δʢͱ͍͏͔ಡΊͳ͍ʣ• ಋೖʹҰखؒඞཁ
protobuf ϝϦοτׂѪ͠·͢ɻϝϦοτͨ͘͞Μ͋ΔͷͰɺσʔλܗࣜͷબࢶͷҰͭʹೖΕΔͷ͋Γͩͱࢥ͍·͢ɻ
gRPC × protobuf• gRPC͕ར༻͢ΔσϑΥϧτͷσʔλܗ͕ࣜprotobuf• gRPC͋͘·Ͱ௨৴ͷखஈͳͷͰɺσʔλܗࣜҰԠjsonͱ͔Մೳ• ͚ͲɺprotobufͱgRPCζϒζϒͳͷͰҰॹʹ͏લఏ
SwiftGRPC
SwiftGRPC• objective-cͰc++Ͱ࣮͞ΕͨgrpcΛར༻2• swiftͰར༻͢Δʹগʑ໘2 https://github.com/grpc/grpc
SwiftGRPC• grpcͷΠϯλʔϑΣʔεΛswiftͰϥοϓ
Officially Supported Platforms3C/C++, C#, Dart *, Go, Java, Node.js, PHP *, Python, Ruby* ·ͩbetaSwiftΞϯΦϑΟγϟϧͰ͔͢?!3 https://grpc.io/about/#osp
ಋೖ
ಋೖ• ͱʹ͔͘protobufલఏ• SwiftGRPCͷprotocϓϥάΠϯΛ࡞• .protoϑΝΠϧ͔Β.swiftϑΝΠϧΛ࡞
protobufΛΠϯετʔϧ$ brew install protobuf
SwiftGRPCͷprotocϓϥάΠϯΛ࡞• grpc-swiftΛcloneͯ͠build$ git clone https://github.com/grpc/grpc-swift.git$ cd grpc-swift$ make all> .protoc-gen-swift .protoc-gen-swiftgrpc
ඞཁʹԠͯ͡pathΛ௨͢• ࣮ߦ࣌ʹϓϥάΠϯͷpathΛࢦఆͰ͖ΔͷͰඞਢͰͳ͍$ mkdir ~/.protoc$ cp ./protoc-gen-swift ./protoc-gen-swiftgrpc ~/.protoc$ echo 'export PATH=$PATH:$HOME/.protoc' >> ~/.bash_profile$ source ~/.bash_profile
protoc-gen-swiftgrpc• SwiftGRPCͷprotocϓϥάΠϯ• protobufͱಉ༷ʹ.proto͔Β.swiftΛ࡞
echo.protoʢbeforeʣsyntax = "proto3";package echo;message EchoRequest {string text = 1;}message EchoResponse {string text = 1;}
echo.protoʢserviceʣ• serviceͷதʹɺrpcϑΟʔϧυΛఆٛ͢Δservice Echo {rpc}
echo.protoʢserviceʣ• rpcϑΟʔϧυʹҙͷϝιουΛఆٛ͢Δservice Echo {rpc Get}
echo.protoʢserviceʣ• ϦΫΤετͷΦϒδΣΫτΛఆٛ͢Δservice Echo {rpc Get(EchoRequest)}
echo.protoʢserviceʣ• ϨεϙϯεͷΦϒδΣΫτΛఆٛ͢Δservice Echo {rpc Get(EchoRequest) returns(EchoResponse) {}}
echo.protoʢserviceʣ• streamෳճͷॲཧΛՄೳʹ͢Δఆٛservice Echo {rpc Get(EchoRequest) returns (EchoResponse) {}rpc Expand(EchoRequest) returns (stream EchoResponse) {}rpc Collect(stream EchoRequest) returns (EchoResponse) {}rpc Update(stream EchoRequest) returns (stream EchoResponse) {}}
echo.protoʢafterʣsyntax = "proto3";package echo;message EchoRequest {string text = 1;}message EchoResponse {string text = 1;}service Echo {rpc Get(EchoRequest) returns (EchoResponse) {}rpc Expand(EchoRequest) returns (stream EchoResponse) {}rpc Collect(stream EchoRequest) returns (EchoResponse) {}rpc Update(stream EchoRequest) returns (stream EchoResponse) {}}
protoc-gen-swiftgrpc$ protoc echo.proto --plugin=./protoc-gen-swiftgrpc --swiftgrpc_out=.
echo.grpc.swiftprotocol Echo_EchoProvider: ServiceProvider {func get(request: Echo_EchoRequest, session: Echo_EchoGetSession) throws -> Echo_EchoResponsefunc expand(request: Echo_EchoRequest, session: Echo_EchoExpandSession) throws -> ServerStatus?func collect(session: Echo_EchoCollectSession) throws -> Echo_EchoResponse?func update(session: Echo_EchoUpdateSession) throws -> ServerStatus?}protocol Echo_EchoService: ServiceClient {func get(_ request: Echo_EchoRequest) throws -> Echo_EchoResponsefunc get(_ request: Echo_EchoRequest, completion: @escaping (Echo_EchoResponse?, CallResult) -> Void) throws -> Echo_EchoGetCallfunc expand(_ request: Echo_EchoRequest, completion: ((CallResult) -> Void)?) throws -> Echo_EchoExpandCallfunc collect(completion: ((CallResult) -> Void)?) throws -> Echo_EchoCollectCallfunc update(completion: ((CallResult) -> Void)?) throws -> Echo_EchoUpdateCall}
EchoʢαϯϓϧʣͰίʔυղઆ
Echoͷಈ͔͠ํ• SwiftGRPC.xcodeprojΛ࡞͢Δ$ make project
Echoͷಈ͔͠ํ• ./third_party/swift-protobuf͕ඞཁ• ࠷ۙऔಘεΫϦϓτ͕আ͞Εͨ4$ mkdir third_party$ cd third_party$ git clone https://github.com/apple/swift-protobuf.git4 https://github.com/grpc/grpc-swift/pull/296
Echoͷಈ͔͠ํ• ./Examples/EchoXcode/Echo.xcodeprojΛ։͘
Echoͷಈ͔͠ํ• ./SwiftGRPC.xcodeprojΛϓϩδΣΫτʹՃ͢Δ
Echoͷಈ͔͠ํ• Target DependenciesʹSwiftGRPC.frameworkΛՃ͢Δ
Echo for Client
Echo_EchoServiceClient• ΫϥΠΞϯτ͔ΒαʔόͷଓΛཧ• ෦ͰChannelΦϒδΣΫτΛอ࣋• Echo_EchoServiceʹ४ڌ• get, expand, collect, updateϝιουΛ࣮ߦͯ͠ɺετϦʔϛϯάΛߦ͏ͨΊͷCallΦϒδΣΫτΛ࡞
Echo_EchoServiceClientlet service = Echo_EchoServiceClient(address: "YOUR_ADDRESS",secure: false)
Echo_EchoExpandCallʢServerStreamingʣ• expand()ϝιουͰऔಘ• αʔόετϦʔϛϯά͕Մೳ• receive()ϝιουΛ࣮ߦՄೳ
Echo_EchoExpandCallʢServerStreamingʣvar requestMessage = Echo_EchoRequest()requestMessage.text = "message"let expandCall = try service.expand(requestMessage) { _ in}try expandCall.receive { response in// ϨεϙϯεΛड͚औΔ}
Echo_EchoCollectCallʢClientStreamingʣ• collect()ϝιουͰऔಘ• ΫϥΠΞϯτετϦʔϛϯά͕Մೳ• send(), closeAndReceive()ϝιουΛ࣮ߦՄೳ
Echo_EchoCollectCallʢClientStreamingʣlet collectCall = try service.collect { _ in}var requestMessage = Echo_EchoRequest()requestMessage.text = "message"try collectCall.send(requestMessage) { error in// ΤϥʔΛड͚औΔ}try collectCall.closeAndReceive { response in// close࣌ʹ1͚ͩσʔλΛड͚औΕΔ}
Echo_EchoUpdateCallʢBidirectionalStreamingʣ• update()ϝιουͰऔಘ• ํετϦʔϛϯά͕Մೳ• send(), receive(), closeSend()ϝιουΛ࣮ߦՄೳ
Echo_EchoUpdateCallʢBidirectionalStreamingʣlet updateCall = try service.update { _ in}try updateCall.receive { response in// ϨεϙϯεΛड͚औΔ}var requestMessage = Echo_EchoRequest()requestMessage.text = "message"try updateCall.send(requestMessage) { error in// ΤϥʔΛड͚औΔ}try updateCall.closeSend {}
ΫϥΠΞϯτ࣮ͷجຊతͳྲྀΕ• Service͔ΒCallΦϒδΣΫτΛ࡞͢Δ• ࡞ͨ͠CallΦϒδΣΫτʹ༻ҙ͞Ε͍ͯΔstreamingͷϝιουΛݺͼग़͢• CallΦϒδΣΫτstreaming͕ऴྃ͢Δ·Ͱอ͓࣋ͯ͘͠※ Unaryྫ֎
Echo_EchoGetCallʢUnaryʣ• get()ϝιουͰऔಘ• streaming༻ͷϝιου༻ҙ͞Ε͍ͯͳ͍
Echo_EchoGetCallʢUnaryʣvar requestMessage = Echo_EchoRequest()requestMessage.text = "message"let getCall = try service.get(requestMessage) { response, callResult in}
ΫϥΠΞϯτ࣮ͷಛ• CallΦϒδΣΫτΛ࡞͔ͬͨ࣌ΒtimeoutΧϯτμϯ• σϑΥϧτ600ඵͰɺϦΫΤετݸผʹઃఆෆՄ• ClientStreamingͷsend()ʹҾͰͤΔtimeoutɺಉظ௨৴͢Δࡍͷsemaphoreͷػ࣌ؒ
ΫϥΠΞϯτ࣮ͷಛ• receive()ɺ1ͰσʔλΛड͚औΔͱͦΕҎ࣮߱ߦ͞Εͳ͍ͷͰɺ࠶receive()ΛݺͿඞཁ͕͋Δ• ͳʹ͔͠ΒͷΤϥʔ͕ग़ͨΒͦΕҎ߱ૹड৴Ͱ͖ͳ͍ͷͰɺCallΦϒδΣΫτ͔Β࠶࡞͢Δඞཁ͕͋Δ• throws͕ͨΒଟ͍
ΫϥΠΞϯτͷstubʹ͍ͭͯ• ServerΛϩʔΧϧʹ࣮ͯ͠stubԽ
Echo for Server
ServiceServer• αʔόͷΫϥΠΞϯτ͔ΒͷଓΛཧ• ෦ͰServerΦϒδΣΫτΛอ࣋• Echo_EchoProviderʹ४ڌͨ͠ΦϒδΣΫτΛड͚औΔ• startϝιουͰɺଓͷड৴Λ։࢝
ServiceServerclass EchoProvider: Echo_EchoProvider {func get() {}func expand() {}func collect() {}func update() {}}let provider = EchoProvider()let server = ServiceServer(address: "YOUR_ADDRESS",serviceProviders: [provider])server.start()
Echo_EchoProvider• get, expand, collect, updateϝιουͰɺΫϥΠΞϯτͷϨεϙϯεΛߦ͏• ҾͰ͞ΕΔSessionΦϒδΣΫτͰৼΔ͍Λ࣮
Echo_EchoProviderʢget == Unaryʣ• Echo_EchoRequestΦϒδΣΫτΛऔಘ• Echo_EchoResponseΛฦͨ͠Βऴྃ
Echo_EchoProviderʢget == Unaryʣfunc get(request: Echo_EchoRequest,session _: Echo_EchoGetSession) throws -> Echo_EchoResponse {var response = Echo_EchoResponse()response.text = request.text + " response"return response}
Echo_EchoProviderʢexpand == ServerStreamingʣ• Echo_EchoRequestΦϒδΣΫτΛऔಘ• ඞཁʹԠͯ͡sessionͷsend()Λ࣮ߦ• ServerStatusΛฦ͠ɺ͔ͭsend()͕ྃͨ͠Βऴྃ
Echo_EchoProviderʢexpand == ServerStreamingʣfunc expand(request: Echo_EchoRequest,session: Echo_EchoExpandSession) throws -> ServerStatus? {var response = Echo_EchoResponse()response.text = request.text + " response"try! session.send(response) {// ΤϥʔΛड͚औΔ}return .ok}
Echo_EchoProviderʢcollect == ClientStreamingʣ• ϦΫΤετsessionͷreceive()Ͱऔಘ• Echo_EchoResponseΛฦͨ͠Βऴྃ
Echo_EchoProviderʢcollect == ClientStreamingʣfunc collect(session: Echo_EchoCollectSession) throws -> Echo_EchoResponse? {var response = Echo_EchoResponse()let request = try! session.receive()response.text = request.text + " response"return response}
Echo_EchoProviderʢupdate == BidiStreamingʣ• ϦΫΤετsessionͷreceive()Ͱऔಘ• ඞཁʹԠͯ͡sessionͷsend()Λ࣮ߦ• ServerStatusΛฦ͠ɺ͔ͭsend()͕ྃͨ͠Βऴྃ
Echo_EchoProviderʢupdate == BidiStreamingʣfunc update(session: Echo_EchoUpdateSession) throws -> ServerStatus? {let request = try! session.receive()var response = Echo_EchoResponse()response.text = request.text + " response"try! session.send(response) {// ΤϥʔΛड͚औΔ}return .ok}
αʔό࣮ͷجຊతͳྲྀΕ• Echo_EchoProviderʹ४ڌͨ͠ΦϒδΣΫτΛ࡞͢Δ• ProviderͷϝιουͰಉظతʹॲཧ͠ɺΛฦͯ͠ऴྃ
SwiftGRPCClient
SwiftGRPCClient> https://github.com/cats-oss/grpc-swift-client• ΫϥΠΞϯτઐ༻ͷgRPCϥΠϒϥϦ• SwiftGRPCʹґଘ• timeoutΛϦΫΤετ୯ҐͰܾΊͨΓ• ϦΫΤετͷ్தͰॲཧΛΠϯλʔηϓτͨ͠Γ• receive()ΛࣗಈͰϦτϥΠͯ͘͠ΕͨΓ
protoc-gen-swiftgrpc-client$ protoc echo.proto --plugin=./protoc-gen-swiftgrpc-client --swiftgrpc-client_out=.
protoc-gen-swiftgrpc-clientenum Echo_EchoMethod: String, CallMethod {case get = "Get"static let service = "echo.Echo"}protocol _Echo_EchoGetRequest {typealias InputType = Echo_EchoRequesttypealias OutputType = Echo_EchoResponse}protocol Echo_EchoGetRequest: _Echo_EchoGetRequest, UnaryStreamingRequest {}extension Echo_EchoGetRequest {var method: CallMethod {return Echo_EchoMethod.get}}
SwiftGRPCʢClientStreamingʣlet collectCall = try service.collect { _ in}var requestMessage = Echo_EchoRequest()requestMessage.text = "message"try collectCall.send(requestMessage) { error in// ΤϥʔΛड͚औΔ}try collectCall.closeAndReceive { response in// close࣌ʹ1͚ͩσʔλΛड͚औΕΔ}
SwiftGRPCClientʢClientStreamingʣlet stream = Session.shared.stream(with: EchoClientRequest())stream.send("message") { response in// ૹ৴݁ՌΛड͚औΔ}stream.closeAndReceive { response in// close࣌ʹ1͚ͩσʔλΛड͚औΕΔ}
SwiftGRPCʢServerStreamingʣvar requestMessage = Echo_EchoRequest()requestMessage.text = "message"let expandCall = try service.expand(requestMessage) { _ in}try expandCall.receive { response in// ϨεϙϯεΛड͚औΔ}
SwiftGRPCClientʢServerStreamingʣlet stream = Session.shared.stream(with: EchoServerRequest(text: "message")).receive { response in// ϨεϙϯεΛड͚औΔ}
SwiftGPRCClientʢRxʣextension Reactive where Base: Streaming, Base.Request: UnaryRequest {func data() -> Observable {return .create { observer inself.base.data { result inswitch result {case .success(let data):observer.onNext(data)observer.onCompleted()case .failure(let error):observer.onError(error)}}return Disposables.create {self.base.cancel()}}}}
SwiftGPRCClientʢRxʣSession.shared.stream(with: EchoUnaryRequest()).rx.data().subscribe(onNext: { response in// ϨεϙϯεΛड͚औΔ}, onError: { error in// ΤϥʔΛड͚औΔ}).disposed(by: disposeBag)
SwiftGPRCClient• ΫϥΠΞϯτʹSwiftGPRCClientΛར༻ͯ͠ɺstubͱͯ͠SwiftGRPCͷαʔό࣮Λར༻͢Δ
ॴײͱࠓޙ
ॴײ• ࠷ॳcallͷcancel͢Βແ͔ͬͨ• ಋೖ͕ਏ͔ͬͨ• Τϥʔ࣌ͷࡉ͔͍ڍಈ͕௫ΈͮΒ͍
Swift Summit
ࠓޙ• ࠷ۙυΠπਓͷΤϯδχΞ͕ฃಆͯ͠ϝϯςφϯε• Network.frameworkͷಋೖͷݕ౼ͳͲ͕͞Ε͍ͯΔ• ͱΓ͋͑ͣਖ਼ࣜʹgrpcҰʹೖͬͯཉ͍͠
grpc-swiftΛͬͯiOSΞϓϦͰշదͳgRPC௨৴Λߦ͏ɹɹhttps://github.com/cats-oss/grpc-swift-clientGithub : KyoheiG3Twitter : @KyoheiG3
Thanks!