問題在手機應用的開發中,一般會將複雜的業務邏輯層實現放在服務端,客戶端僅負責表現層。可是對於某些手機應用而言,業務邏輯的實現位於服務端反而是不安全的或是不合理的,而是須要將其邏輯直接在手機端實現。java
目的ios
面對不一樣系統的手機客戶端,單獨重複實現相同的業務邏輯,並不是最佳實踐。如何經過第三方語言 Go 語言將業務邏輯封裝成庫的形式,並以靜態打包的方式提供給不一樣系統的手機客戶端使用,是本次調研的目的。git
理想目標圖:程序員
具體調研內容包括:github
其中關於 gRPC 在 iOS 與 Android 的實現,自己官方就已經提供了樣例。本次調研會用到相關內容,因此將其做爲調研的一部分記錄下來,方便後來者閱讀。調研中全部涉及的項目代碼均存放於: liujianping/grpc-apps 倉庫中, 須要的朋友能夠直接下載測試。golang
原文發佈在個人我的站點: GitDiG.com. 原文連接: iOS 應用實現 gRPC 調用 .
做爲一名非專職 iOS 的程序員,常常須要調研陌生的技術或者語言。首先是要克服對於未知的畏懼心理。其實不少東西沒那麼難,只是須要開始而已。 爲了完成目標調研,開始第一部分的調研工做。以文字形式記錄下來,方便後來者。swift
沒什麼好說的,直接 AppStore 下載安裝。有點慢,一邊下載一邊準備其它環境。vim
相似與其它語言的第三方庫管理工具。也沒什麼好說的,登陸官網,按說明安裝。後端
$: sudo gem install cocoapods
由於 gRPC 的普遍使用, ProtoBuf 協議被普遍用於字節編碼與解碼的協議, 其具體指南參考[官網]()。話很少說,安裝:xcode
$: curl -LOk https://github.com/protocolbuffers/protobuf/releases/download/v3.5.1/protoc-3.9.0-rc-1-osx-x86_64.zip $: unzip protoc-3.9.0-rc-1-osx-x86_64.zip -d proto_buffer && cd proto_buffer $: sudo cp bin/protoc /usr/local/bin $: sudo cp -R include/google/protobuf/ /usr/local/include/google/protobuf $: protoc --version
protoc 主要是經過解析 .proto
格式的文件, 再根據具體插件生成相應語言代碼。
考慮到須要同時實現客戶端與服務端的代碼,因此必須安裝如下三個插件:
swift 插件安裝:
$: git clone https://github.com/grpc/grpc-swift.git $: cd grpc-swift $: git checkout tags/0.5.1 $: make $: sudo cp protoc-gen-swift protoc-gen-swiftgrpc /usr/local/bin
go 插件安裝:
前提是須要安裝 Go 語言的開發環境, 可參考官網。protoc-gen-go
安裝詳細指南.
$: go get -u github.com/golang/protobuf/protoc-gen-go
既然是最簡單的調研,就用最簡單的 Hello 服務。建立項目路徑並定義:
$: mkdir grpc-apps $: cd grpc-apps $: mkdir proto $: cat <<EOF > proto/hello.proto syntax = "proto3"; option java_multiple_files = true; option java_package = "com.gitdig.helloworld"; option java_outer_classname = "HelloWorldProto"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; } EOF
在項目目錄中建立服務端目錄與proto生成目錄,同時編寫一個簡單的服務端:
$: cd grpc-apps $: mkdir go go/client go/server go/hello # 生成 Go 代碼到 go/hello 文件夾 $: protoc -I proto proto/hello.proto --go_out=plugins=grpc:./go/hello/
分別編輯 Go 版本 client 與 server 實現。確認服務正常運行。
編輯 server/server.go
文件:
package main import ( pb "github.com/liujianping/grpc-apps/go/helloworld" ) import ( "context" "fmt" "log" "net" "google.golang.org/grpc" ) type HelloServer struct{} // SayHello says 'hi' to the user. func (hs *HelloServer) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloReply, error) { // create response res := &pb.HelloReply{ Message: fmt.Sprintf("hello %s from go", req.Name), } return res, nil } func main() { var err error // create socket listener l, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("error: %v\n", err) } // create server helloServer := &HelloServer{} // register server with grpc s := grpc.NewServer() pb.RegisterGreeterServer(s, helloServer) log.Println("server serving at: :50051") // run s.Serve(l) }
運行服務端程序:
$: cd grpc-apps/go $: go run server/server.go 2019/07/03 20:31:06 server serving at: :50051
編輯 client/client.go
文件:
package main import ( pb "github.com/liujianping/grpc-apps/go/helloworld" ) import ( "context" "fmt" "log" "google.golang.org/grpc" ) func main() { var err error // connect to server conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatalf("error: %v\n", err) } defer conn.Close() // create client client := pb.NewGreeterClient(conn) // create request req := &pb.HelloRequest{Name: "JayL"} // call method res, err := client.SayHello(context.Background(), req) if err != nil { log.Fatalf("error: %v\n", err) } // handle response fmt.Printf("Received: \"%s\"\n", res.Message) }
執行客戶端程序:
$: cd grpc-apps/go $: go run client/client.go Received: "hello JayL from go"
Go 客戶端/服務端通訊成功。
建立一個名爲 iosDemo 的單視圖項目,選擇 swift 語言, 存儲路徑放在 grpc-apps
下。完成建立後,正常運行,退出程序。
在命令行執行初始化:
$: cd grpc-apps/iosDemo # 初始化 $: pod init $: vim Podfile
編輯 Podfile 以下:
# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'iosDemo' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for iosDemo pod 'SwiftGRPC' end
完成編輯後保存,執行安裝命令:
$: pod install
安裝完成後,項目目錄發生如下變動:
$: git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: iosDemo.xcodeproj/project.pbxproj Untracked files: (use "git add <file>..." to include in what will be committed) Podfile Podfile.lock Pods/ iosDemo.xcworkspace/ no changes added to commit (use "git add" and/or "git commit -a")
經過命令行 open iosDemo.xcworkspace
打開項目,對項目中的info.list的如下設置進行修改:
經過設置,開啓非安全的HTTP訪問方式。
相似 Go 代碼生成,如今生成 swift 代碼:
$: cd grpc-apps # 建立生成文件存放目錄 $: mkdir swift # 生成 swift 文件 $: protoc -I proto proto/hello.proto \ --swift_out=./swift/ \ --swiftgrpc_out=Client=true,Server=false:./swift/ # 生成文件查看 $: tree swift swift ├── hello.grpc.swift └── hello.pb.swift
XCode中添加生成代碼須要經過拖拽的方式,對於後端開發而言,確實有點不可理喻。不過既然必須這樣就按照規則:
如今在 iOS 的視圖加載函數增長 gRPC 調用過程:
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. let client = Helloworld_GreeterServiceClient(address: ":50051", secure: false) var req = Helloworld_HelloRequest() req.name = "JayL" do { let resp = try client.sayHello(req) print("resp: \(resp.message)") } catch { print("error: \(error.localizedDescription)") } } }
查看日誌輸出resp: hello iOS from go
, iOS 應用調用 gRPC 服務成功。