Google Cloud EndpointsをSwiftで扱う

iOSアプリでSwiftを利用しGoogle Cloud Endpointsを扱ってみました。

現段階ではiOS用のライブラリはObjective-Cのみしか生成できないため、Objective-Cで書かれたライブラリをSwiftで扱っていきます。
今回はGAEプロジェクト作成等はできている前提でそれらについては詳しくは触れません。

#大まかな流れ#
1.Endpointクラスファイルの作成
2.クライアントライブラリの生成
3.iOS用のクライアントライブラリの生成
4.クライアントライブラリの導入
5.iOSアプリの実装


こんな感じの手順で実装していきます。
5をSwiftで実装する以外は特にObjective-Cで扱うのと違いはありません。

#今回使用するDTO#
今回はGAEのDatastoreを利用するため、DTOクラスファイルを用意します。
EclipseでJavaプロジェクトで作成しました。

ここで使用するのは、クラス名が**TestSwiftData**で、フィールドに
**String id,String message,Date date**
を持つだけのシンプルなクラスです。


#1.Endpointクラスファイルの作成#
まずDTOクラスのデータを扱うためのAPIを実装したEndpointクラスをEclipse上で作成します。
ここではTestSwiftEndpointクラスとしています。

![スクリーンショット 2014-12-05 17.02.53.png](https://qiita-image-store.s3.amazonaws.com/0/57702/adc541be-247e-13ee-4955-d5c39e9a4650.png)



```TestSwiftEndpoint

@Api(name = "testSwiftEndpoint", version = "v1",description="endpoint test with swift")
public class TestSwiftEndpoint{
	public TestSwiftEndpoint(){
		super();
	}
	
	//IDを指定して取得するためのAPIメソッド
	@ApiMethod(name="get",path="get/{id}",httpMethod = HttpMethod.GET)
	public TestSwiftData get(@Named("id") String id) throws EntityNotFoundException{
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
		Key key = KeyFactory.createKey("Test", id);
		Entity entity = ds.get(key);
		String message = (String)entity.getProperty("message");
		Date date = (Date)entity.getProperty("date");
		TestSwiftData tsd = new TestSwiftData(id,message,date);
		return tsd;
	}
	
	//IDを指定して削除するためのAPIメソッド
    @ApiMethod(name="remove",path="remove/{id}",httpMethod=HttpMethod.DELETE)
	public TestSwiftData remove(@Named("id") String id)throws EntityNotFoundException{
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
		Key key = KeyFactory.createKey("Test", id);
		Entity entity = ds.get(key);
		String message = (String)entity.getProperty("message");
		Date date = (Date)entity.getProperty("date");
		
		ds.delete(key);
		TestSwiftData tsd = new TestSwiftData(id,message,date);
		return tsd;
	}
	
	//エンティティを保存するAPIメソッド
	@ApiMethod(name="put",path="put",httpMethod=HttpMethod.PUT)
	public TestSwiftData put(@Named("id") String id,@Named("message") String message){
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
		Key key = KeyFactory.createKey("Test", id);
		Entity entity;
		try{
			entity = ds.get(key);
		}catch(EntityNotFoundException e){
			entity = new Entity("Test",id);	
		}
		Date now = new Date();
		entity.setProperty("date", now);
		entity.setProperty("message", message);
		ds.put(entity);
		TestSwiftData tsd = new TestSwiftData(id,message,now);
		return tsd;
	}
	
	//全エンティティを取得するAPIメソッド
	@ApiMethod(name="list",path="list",httpMethod=HttpMethod.GET)
	public List<TestSwiftData> list(){
		DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
		FetchOptions options = FetchOptions.Builder.withLimit(10);
		Query query = new Query("Test");
		query.addSort("date",SortDirection.DESCENDING);
		List<Entity> entities = ds.prepare(query).asList(options);
		List<TestSwiftData> tsds = new ArrayList<TestSwiftData>();
		for(Entity entity: entities){
			String id = (String)entity.getKey().getName();
			String message = (String)entity.getProperty("message");
			Date date = (Date)entity.getProperty("date");
			TestSwiftData tsd = new TestSwiftData(id,message,date);
			tsds.add(tsd);
		}
		return tsds;
	}
}
```

簡単に解説をすると、まず先頭で**@Api**によりこのクラスをAPIクラスと明示しています。

また各操作のためのメソッドに**@ApiMethod**でAPIメソッドとして明示をし設定をしています。
Datastoreの操作にはDatastoreServiceを利用しています。

DTOクラスファイル、Endpointクラスファイルの準備ができたらApp Engineにデプロイします。
ここでは省略します。

#2.クライアントライブラリの生成

##Google APIs クライアントライブラリ

iOSクライアントライブラリ生成のための生成プロジェクト、Google APIs クライアントライブラリ for Objective-Cをダウンロードします。

ターミナルで以下を入力し、SVNコマンドでチェックアウトします。

```
svn checkout \
    http://google-api-objectivec-client.googlecode.com/svn/trunk/ \
    google-api-objectivec-client-read-only
```

![スクリーンショット 2014-12-05 17.34.46.png](https://qiita-image-store.s3.amazonaws.com/0/57702/1057d61f-9adf-272b-2d0d-96038d139990.png)


![スクリーンショット 2014-12-05 17.35.43.png](https://qiita-image-store.s3.amazonaws.com/0/57702/f9235cbd-c9d9-d454-90aa-f344d154d424.png)
これで任意のフォルダに一式がダウンロードされます。

![スクリーンショット 2014-12-05 20.07.21.png](https://qiita-image-store.s3.amazonaws.com/0/57702/e9462249-7d86-0d72-cffd-bcc0015e1690.png)


ダウンロードされたフォルダ内の
google-api-objectivec-client-read-only ▸ Source ▸ ToolsにあるXcodeプロジェクト「ServiceGenerator.xcodeproj」を開きそのままRunで実行をします。

実行後、同プロジェクト内のProductsフォルダにあるServiceGeneratorと、先ほどEclipse上で作成したEndpointプロジェクト内にある**rpc.discovery**のそれぞれのパスを使い作成したプロジェクト用の専用クラスライブラリを生成します。

![スクリーンショット 2014-12-02 16.55.39.png](https://qiita-image-store.s3.amazonaws.com/0/57702/12f25b0d-7083-797c-a4ab-4f3745855252.png)

![スクリーンショット 2014-12-05 17.46.27.png](https://qiita-image-store.s3.amazonaws.com/0/57702/55d97f1a-d210-63e4-59b0-486eb688927c.png)

ターミナルから
```
(ServiceGeneratorのパス)(半角スペース)(rpc.discoveryのパス) --outputDir ./API
```

を実行します。
すると以下のような結果が表示されます。

![スクリーンショット 2014-12-02 16.59.40.png](https://qiita-image-store.s3.amazonaws.com/0/57702/5d87baa4-13e9-5404-8a46-ca0f782c6908.png)


これで任意のフォルダにAPIフォルダとして生成されます。

>Using Endpoints in an iOS Client
>https://cloud.google.com/appengine/docs/java/endpoints/consume_ios

#4.クライアントライブラリの導入
先ほどXcodeで作成したクライアントアプリに生成したクラスライブラリを導入します。
生成されたAPIフォルダ内のクラスライブラリをXcode上のクライアントライブラリにコピーします。
![スクリーンショット 2014-12-05 18.59.18.png](https://qiita-image-store.s3.amazonaws.com/0/57702/b2df097b-d206-fb58-ebc0-bf20d4205a48.png)


次に、ダウンロードした「google-api-objectivec-client-read-only」内にあるXcodeプロジェクトGTL.xcodeprojを開きます。

![スクリーンショット 2014-12-05 19.02.16.png](https://qiita-image-store.s3.amazonaws.com/0/57702/4578a03b-50f8-2317-8c5b-4dba892e917f.png)

そしてこのプロジェクトに含まれている、commonフォルダ内のすべてのソースファイルも同様にクライアントアプリにコピーします。

![スクリーンショット 2014-12-05 19.03.25.png](https://qiita-image-store.s3.amazonaws.com/0/57702/5bf0e648-85b0-2dd9-bded-13757dbef795.png)



ここではGCEフォルダにまとめています。
![スクリーンショット 2014-12-05 19.05.17.png](https://qiita-image-store.s3.amazonaws.com/0/57702/6e263dc7-d844-65f0-d8fe-5b716589fdef.png)


その後、このクライアントアプリのBuildPhasesからCompile Sourcesを開き、

　　![スクリーンショット 2014-12-05 19.08.14.png](https://qiita-image-store.s3.amazonaws.com/0/57702/bf7d3b02-457b-f103-931d-1292542f3b56.png)

GTL(TestSwiftEndpoint)_Souces.mをビルド対象から除外します。
カッコ内は作成したEndpointクラスです。

![スクリーンショット 2014-12-05 19.10.56.png](https://qiita-image-store.s3.amazonaws.com/0/57702/9398fca0-b70b-903e-285d-88f53cc083b4.png)

そして追加してすべてのファイルを選択しCompiler Flagsに「-fno-objc-arc」を設定しARCを無効にします。
画像だとswiftファイルも選択されてしまっていますが、ARCを無効にするのは先ほど追加したファイルのみです。

![スクリーンショット 2014-12-05 19.14.06.png](https://qiita-image-store.s3.amazonaws.com/0/57702/894215f7-b3c8-624b-093d-9bc3dc2ad8bf.png)

最後に、GeneralからSecurity.framework、SystemConfiguration.frameworkを追加します。

![スクリーンショット 2014-12-05 19.16.31.png](https://qiita-image-store.s3.amazonaws.com/0/57702/5c3d418f-6760-9e5d-20e9-84437cd88225.png)

以上で導入の完了です。

#5.iOSアプリの実装
クライアントアプリの実装をしていきます。
目的は「取得」ボタンを押すとクライアントライブラリからDatastoreに登録されているすべてのエンティティを取得し、アプリ内のTextViewに表示されることです。

```ViewController.swift
//取得ボタンのアクション
@IBAction func getTestSwiftData(sender: AnyObject) {
        var service:GTLServiceTestSwiftEndpoint?
        if (service == nil) {
            service = GTLServiceTestSwiftEndpoint()
            service!.retryEnabled = true
            GTMHTTPFetcher.setLoggingEnabled(true)
        }
        var query = GTLQueryTestSwiftEndpoint.queryForList() as GTLQueryTestSwiftEndpoint
        
        service?.executeQuery(query, completionHandler: {(ticket,object,error) in
            var items:NSArray = object.items!
            println(items)
            var messageAry = NSMutableArray()
            for(var i=0;i<items.count;i++) {
                var dto:GTLTestSwiftEndpointTestSwiftData?
                dto = (items[i] as GTLTestSwiftEndpointTestSwiftData)
                
                var id = dto?.identifier
                var message = dto?.message
                var date = dto?.date
                var dateStr = NSString(format: "%@", date!)
                
                //var formatter = NSDateFormatter().dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
                
                var messageText = NSString(format:"%@: \n%@ (ID:%@)\n", dateStr,message!,id!)
                messageAry.addObject(messageText)
            }
            self.textView.text = messageAry.componentsJoinedByString("\n")
       
```

次にObjective-C用のライブラリをSwiftファイルで利用するためにBridging-Headerファイルを作成します。

```TestEndpointWithSwift-Bridging-Header
#import "GTMHTTPFetcherLogging.h"
#import "GTLServiceTestSwiftEndpoint.h"
#import "GTLQueryTestSwiftEndpoint.h"
#import "GTLTestSwiftEndpointTestSwiftData.h"
```


以上です。
ここでは一覧取得のクエリを作り実行し、エンティティを取得しています。

#動かしてみる
実際にクライアントアプリを動かしてみました。

##エンティティの準備
その前に取得するエンティティデータを用意します。
今回はブラウザのAPI explorerから以下のデータを作成しておきました。

API explorerはhttps://(プロジェクトID).appspot.com/_ah/api/explorerから。
![スクリーンショット 2014-12-05 19.30.54.png](https://qiita-image-store.s3.amazonaws.com/0/57702/5db8801c-bbc4-ec37-2cd3-2b834f7b712b.png)

putのAPIを利用して、

![スクリーンショット 2014-12-05 19.33.04.png](https://qiita-image-store.s3.amazonaws.com/0/57702/c2281d28-8a7f-a6be-60a6-3807da72d3ca.png)

３つのエンティティを作成しました。
これをアプリから取得し表示されます。

##実行
取得ボタンを押すと、

![iOS Simulator Screen Shot 2014.12.05 19.34.47.png](https://qiita-image-store.s3.amazonaws.com/0/57702/fd30d358-1a62-6872-f8a2-39aea90efeba.png)

エンティティを取得しUITextViewに出力されました。

![iOS Simulator Screen Shot 2014.12.05 19.34.50.png](https://qiita-image-store.s3.amazonaws.com/0/57702/d765f058-d3aa-7de4-03a2-913ba895d5f5.png)
取得の他にもPut(.queryForPutWithIdentifier)やRemove(.queryForRemoveWithIdentifier)なども同様に行えます。

ということで簡単なエンティティ取得アプリをSwiftで動かすことができました。