ココ数ヶ月Java書いてない一応Java Engineerの大橋です。
どこもかしこもswiftですね。
iPhone系アプリ開発を一番やりたくない理由がObjective-Cだったのですが、
swiftの登場でやる気が出ました。
swiftはObjective-CのLibraryも呼べるとの事なので、
今回はGoogle Cloud Endpointsで作成したObjective-Cのクライアントライブラリを
swiftから呼び出すって話を書きたいと思います。
まとめ
最終的には以下のコードになります。
所感として
- Swift版だと楽ではある。
- Objective-Cっぽさが残るので、Cloud EndpointsからSwiftのコードを作成して欲しい
- 元々のLibrary作成までの道程が長い(ゲフンゲフン
- Golangが好き
です。
Cloud Endpointsの準備
まずSwiftうんぬんの前に以下のことが準備として必要です。
GAEをやっててGoogle系APIをよく触っている方はみたいでもわかるのでそれぞれ飛ばしても良いと思います。
なお以下の手順はObjective-C用のLibraryを作る手順なのでSwiftを使う予定が無くても使えます。
- Google Developer Console でプロジェクトの作成
- API用のClientId、Secretの作成
- GAE上でCloud Endpointsを利用してAPIを作成(今回はGoを使います。)
- 作成したAPIを元にObjective-CのLibraryを作成
- XCodeでswiftプロジェクトを作成して、作成したLibraryとその他もろもろを読み込む
- XCodeプロジェクトの設定(ARC周り)
Google Developer Console でプロジェクトの作成
↓から適当に作って下さい。
API用のClientId、Secretの作成
作成したプロジェクトでios向けOAuth2認証用のClientIdとSecretを作成します。
- プロジェクト画面を開いて左側メニューの「APIと認証」→「認証情報」を選択
- 「新しいクライアントIDを作成」をクリック
- 「クライアントIDを作成」ダイアログが表示されるので、「インストールされているアプリケーション」を選択、「インストールされているアプリケーションの種類」に「iOS」を選択
- バンドルIDはこの後作成するSwiftプロジェクト(またはObjective-Cプロジェクト)の
{組織ID}.{アプリケーションID}
を入力(Info.plistのBundle identifierの値)、その他は一旦設定しなくても大丈夫です。(クライアントアプリケーションをリリースする場合は他も設定したほうが良いはずです。)
- 「クライアントIDを作成」をクリックしてclient id、client secretが発行されるのでどこかに控えておきます。
GAE上でCloud Endpointsを利用してAPIを作成(今回はGoを使います。)
今回は以下の2つのAPIを作成します。
ざっくり言えば /message というendpointsにGETとPOSTのAPIを作成して、
POSTの方は認証必要にしただけです。
-
/swiftsampleapi/v1/message
- HTTP Method : GET
- パラメータ : 無し
- 動作 : メッセージを返却するのみ
- レスポンス : 以下のプロパティを持ったJSON
- message
- ログイン(認可済み)していない場合は"Hello World!"
- している場合は"Hello {ログインユーザのEmailアドレス}"
- message
-
/swiftsampleapi/v1/message
- HTTP Method : POST
- パラメータ : 以下のプロパティを持ったJSON
- message
- 任意
- message
- 動作 : 渡されたメッセージをDatastoreへ保存 認可済みでない場合はエラーを返す
- レスポンス : 以下のプロパティを持ったJSON
- id
- datastoreの自動で付けられたnumber型のID
- message
- パラメータで渡されたもの
- email
- ログインユーザのEmailアドレス
- registeredAt
- datastoreへ突っ込んだ時間
- id
この実装は以下においてあります。
go-endpointsとgoonを使ってます。
main.go
の定数でclientID、IOS_CLIENT_IDがあるので同じ値で良いので先ほど作成したclient idを設定しておきます。(client secretは後で使います。)
作成したAPIを元にObjective-CのLibraryを作成
ここからが糞めんどくさいです。
以下の様なことを行います。
- GAEへデプロイ
- discovery doc(APIの定義が書かれたJSON Schema ファイル)の取得
- Library作成用のGenerator XCodeプロジェクトをsvn checkoutして XCodeで取り込んでビルド(え?)
- GeneratorからLibraryを作成
公式ドキュメントは以下です。
GAEへデプロイ
デプロイをしていないと2.のdiscovery docが取得できないのでデプロイします。(Goの場合です。 pythonとかJavaはしてなくてもできるはずです。 確か)
app.yamlがあるディレクトリで
$ goapp deploy -oauth
を叩きます。
※Google Cloud SDKとGAE/GのSDKをインストールしておいて下さい
discovery doc(APIの定義が書かれたJSON Schema ファイル)の取得
定義ファイルを取得します。
基本的にはデプロイしたアプリケーションの以下のURLへアクセスすれば取得できます。
https://{google projectのapp id}.appspot.com/_ah/api/discovery/v1/apis/{api名}/v1/rpc
今回だと以下の感じです。
https://swift-cloud-endpoint-sample.appspot.com/_ah/api/discovery/v1/apis/swiftsampleapi/v1/rpc
※当面叩けるように置いておきます
※注意
Android側のAPIを作ったことが有る方だと時々ハマるのですが、上記の末尾のパスがrest
のものもあります。Androidだとrest
の方のパスを使いますが、iosの場合はrpc
じゃないと動かないのでrpcにして下さい。
curlとかで取得する場合は以下の感じですね
$ URL='https://swift-cloud-endpoint-sample.appspot.com/_ah/api/discovery/v1/apis/swiftsampleapi/v1/rpc'
$ curl -s $URL > swiftsampleapi.rpc.discovery
Library作成用のGenerator XCodeプロジェクトをsvn checkoutして XCodeで取り込んでビルド(え?)
ここからが結構大変です。
1.Library作成用のGenerator XCodeプロジェクトをsvn checkout
$ svn checkout \
http://google-api-objectivec-client.googlecode.com/svn/trunk/ \
google-api-objectivec-client-read-only
2.XCode上で上記プロジェクトの
google-api-objectivec-client-read-only/Source/Tools/ServiceGenerator/ServiceGenerator.xcodeproj
をインポートして、ビルドします。(Cmd + B)
※このプロジェクトにはServiceGenerator
というターゲットのみ存在します。
GeneratorからLibraryを作成
ビルドするとServiceGenerator
という実行可能ファイルができるので以下のコマンドを叩きます。
$ /path/to/ServiceGenerator /pathto/{先ほど作成したdiscovery doc} --outputDir ./api
以上で./api
ディレクトリにObjective-CのCloud Endpoints Libraryが作成されます。
./api
├── GTLQuerySwiftsampleapi.h
├── GTLQuerySwiftsampleapi.m
├── GTLServiceSwiftsampleapi.h
├── GTLServiceSwiftsampleapi.m
├── GTLSwiftsampleapi.h
├── GTLSwiftsampleapiConstants.h
├── GTLSwiftsampleapiConstants.m
├── GTLSwiftsampleapiGetRes.h
├── GTLSwiftsampleapiGetRes.m
├── GTLSwiftsampleapiPostReq.h
├── GTLSwiftsampleapiPostReq.m
├── GTLSwiftsampleapiPostRes.h
├── GTLSwiftsampleapiPostRes.m
└── GTLSwiftsampleapi_Sources.m
XCodeでswiftプロジェクトを作成して、作成したLibraryとその他もろもろを読み込む
XCodeでSwiftのプロジェクトを作成し、上記Libraryをプロジェクト内に放り込みます(Drag & Drop)。(Groupとか適当に作って下さい)
この時*_Sources.m
は除いて下さい。重複エラーが出ます。
この時{XCodeプロジェクト名}-Bridging-Header.h
というファイルを作るか聞かれるので、作って下さい。後ほど使います。
またこのLibrary群が依存するLibraryがあります。
それらは、先ほどsvn checkoutしたgoogle-api-objectivec-client-read-only
野中のgoogle-api-objectivec-client-read-only/Source/GTL.xcodeproj
にあるので、
それをXCode上でプロジェクトとしてインポートし、OAuth2/Mac
を除き全て持ってきます。
共に持ってきたら先ほど作成された{XCodeプロジェクト名}-Bridging-Header.h
で読み込みます。
```c:{XCodeプロジェクト名}-Bridging-Header.h
#import "GTMOAuth2ViewControllerTouch.h"
#import "GTMHTTPFetcherLogging.h"
#import "GTLSwiftsampleapi.h"
### XCodeプロジェクトの設定(ARC周り)
このままだとビルドが通らないので、ARCの設定を切ります。
XCodeプロジェクトの「Build Settings」を開き、「Appple LLVM 6.0 - Languege - Objective C」の「Objective-C Automatic Reference Counting」を「No」に設定します。
※よくこの設定が私はまだ理解してないので、ちゃんと調べてやったほうがいい気がしますが...
これで準備は完了です。
## Swift側の実装
あーだこーだ言うよりコード見たほうが早い気がするのでフルコード載せます。
```swift:ViewController.swift
//
// ViewController.swift
// io.github.soundtricker.swift-sample
//
// Created by soundTricker on .
// Copyright (c) 2014 Keisuke Oohashi. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
let KEY_CHAIN_ITEM_NAME : String = "SwiftGCEApp"
//clientId and clientSecret should be make on https://console.developers.google.com/
let CLIENT_ID : String = "your client id"
let CLIENT_SECRET : String = "your client secret"
var signedIn = false
let service : GTLServiceSwiftsampleapi
init(coder aDecoder: NSCoder!) {
service = GTLServiceSwiftsampleapi()
service.retryEnabled = true
GTMHTTPFetcher.setLoggingEnabled(true)
super.init(coder : aDecoder)
}
@IBOutlet var message : UITextField
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func saveMessage(sender : AnyObject) {
if !signedIn {
let alert = UIAlertController(title: "Error", message: "Is not Loggedin", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
return
}
let param = GTLSwiftsampleapiPostReq()
param.message = message.text
let query : GTLQuerySwiftsampleapi = GTLQuerySwiftsampleapi.queryForMessagePostWithObject(param) as GTLQuerySwiftsampleapi
self.service.executeQuery(query, completionHandler: { (ticket : GTLServiceTicket!, object : AnyObject!, error : NSError!) -> Void in
if error != nil {
NSLog("\(error)")
return
}
let res = object as GTLSwiftsampleapiPostRes
self.message.text = "\(res.message) \(res.identifier) \(res.registeredAt) \(res.email)"
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func authorize(sender : AnyObject) {
let viewController = GTMOAuth2ViewControllerTouch(scope : "https://www.googleapis.com/auth/userinfo.email" , clientID: CLIENT_ID, clientSecret:CLIENT_SECRET, keychainItemName : KEY_CHAIN_ITEM_NAME, delegate : self, finishedSelector : "viewController:finishedWithAuth:error:")
self.presentModalViewController(viewController, animated: true)
}
@objc(viewController:finishedWithAuth:error:)
func finishedWithAuth(viewController :GTMOAuth2ViewControllerTouch , finishedWithAuth auth:GTMOAuth2Authentication,error:NSError){
self.dismissModalViewControllerAnimated(true)
if error != nil {
} else {
self.service.authorizer = auth
auth.authorizationTokenKey = "id_token"
signedIn = true
}
}
@IBAction func greeting(sender : AnyObject) {
let query : GTLQuerySwiftsampleapi = GTLQuerySwiftsampleapi.queryForMessageGet() as GTLQuerySwiftsampleapi
self.service.executeQuery(query, completionHandler: { (ticket : GTLServiceTicket!, object : AnyObject!, error : NSError!) -> Void in
if error != nil {
NSLog("\(error)")
return
}
self.message.text = (object as GTLSwiftsampleapiGetRes).message
})
}
}
init
メソッドでクライアントLibraryの起点となるServiceクラスを作成しています。
greeting
メソッドで認証有無に関係ないAPIコールをしています。普通にコールバックがかけて楽ですね。
認証処理はauthorize
メソッドで実施しています。
もともと@selector
というのを使っていたので、
その場合は、対応するメソッドを@objc()
を付けた状態で定義する必要があるみたいです。
※
finishedWithAuth
メソッドがそれ
saveMessage
メソッドで認可状態でのパラメータ付きPostリクエストを送ってます。
こう見るとだいぶ楽ですね。