protocol buffersでObjective Cのクラスを生成してnode.jsサーバーと疎通確認

#環境
 * Mac OS X 10.12 or 10.11.6
 * libprotoc 3.2.0

iOS側
 * XCode 8.1
 * iOS Simulator iOS 10.1
 * CocoaPods 1.1.1

サーバー側
 * node v4.5.0(npm 2.15.9)
 * protocol-buffers@3.2.1
 * connect@3.6.0


#やること
protocol buffersでObjective-Cのクラスを生成
シリアライズ、デシリアライズができることとサーバーとの疎通を確認する

[クラス生成手順](#クラス生成手順)
[CocoapodsでiOSプロジェクトに追加](#CocoapodsでiOSプロジェクトに追加)
[サーバーとの疎通を確認](#サーバーとの疎通を確認)
[エラーメモ](#エラーメモ)


#参考
公式
https://github.com/google/protobuf
公式 - Objective C
https://github.com/google/protobuf/tree/master/objectivec

##クラス生成手順

```bash
#先に必要なものインストール
$ brew install libtool
$ brew install autoconf
$ brew install automake
$ brew install protobuf
#インストール終了
$ protoc --version
libprotoc 3.2.0
```

###レポジトリ落としてきてビルドする場合

```bash
$ git clone https://github.com/google/protobuf.git
$ cd protobuf
$ objectivec/DevTools/full_mac_build.sh

#成功すればsrc/protocに.protoから各言語のクラスを生成できるバイナリができる
$ ln -s src/protoc /usr/local/bin
```

ProtocolBuffersのクラス生成元となる.protoファイルを作る

```bash
$ cd {どこか適当な場所}
$ touch search_request.proto
$ vim search_request.proto
```

```protobuf:search_request.proto
syntax = "proto3";

package test;

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
```
.protoファイルからObjective-Cのクラスを生成
--objc_out={生成先ディレクトリ}オプションで作る

```bash

$ mkdir objc_gen
$ protoc search_request.proto --objc_out=objc_gen
$ ll objc_gen
total 16
-rw-r--r--  1 yamanaka  staff   1.9K  3  9 00:41 SearchRequest.pbobjc.h
-rw-r--r--  1 yamanaka  staff   3.5K  3  9 00:41 SearchRequest.pbobjc.m

```

下記のようなファイルが出来上がった



```objc:objc_gen/SearchRequest.pbobjc.h
// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: search_request.proto

// This CPP symbol can be defined to use imports that match up to the framework
// imports needed when using CocoaPods.
#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS)
 #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0
#endif

#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
 #import <Protobuf/GPBProtocolBuffers.h>
#else
 #import "GPBProtocolBuffers.h"
#endif

#if GOOGLE_PROTOBUF_OBJC_VERSION < 30002
#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources.
#endif
#if 30002 < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION
#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources.
#endif

// @@protoc_insertion_point(imports)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

CF_EXTERN_C_BEGIN

NS_ASSUME_NONNULL_BEGIN

#pragma mark - SearchRequestRoot

/**
 * Exposes the extension registry for this file.
 *
 * The base class provides:
 * @code
 *   + (GPBExtensionRegistry *)extensionRegistry;
 * @endcode
 * which is a @c GPBExtensionRegistry that includes all the extensions defined by
 * this file and all files that it depends on.
 **/
@interface SearchRequestRoot : GPBRootObject
@end

#pragma mark - SearchRequest

typedef GPB_ENUM(SearchRequest_FieldNumber) {
  SearchRequest_FieldNumber_Query = 1,
  SearchRequest_FieldNumber_PageNumber = 2,
  SearchRequest_FieldNumber_ResultPerPage = 3,
};

@interface SearchRequest : GPBMessage

@property(nonatomic, readwrite, copy, null_resettable) NSString *query;

@property(nonatomic, readwrite) int32_t pageNumber;

@property(nonatomic, readwrite) int32_t resultPerPage;

@end

NS_ASSUME_NONNULL_END

CF_EXTERN_C_END

#pragma clang diagnostic pop

// @@protoc_insertion_point(global_scope)
```

```objc:objc_gen/SearchRequest.pbobjc.m
// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: search_request.proto

// This CPP symbol can be defined to use imports that match up to the framework
// imports needed when using CocoaPods.
#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS)
 #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0
#endif

#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
 #import <Protobuf/GPBProtocolBuffers_RuntimeSupport.h>
#else
 #import "GPBProtocolBuffers_RuntimeSupport.h"
#endif

 #import "SearchRequest.pbobjc.h"
// @@protoc_insertion_point(imports)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

#pragma mark - SearchRequestRoot

@implementation SearchRequestRoot

// No extensions in the file and no imports, so no need to generate
// +extensionRegistry.

@end

#pragma mark - SearchRequestRoot_FileDescriptor

static GPBFileDescriptor *SearchRequestRoot_FileDescriptor(void) {
  // This is called by +initialize so there is no need to worry
  // about thread safety of the singleton.
  static GPBFileDescriptor *descriptor = NULL;
  if (!descriptor) {
    GPB_DEBUG_CHECK_RUNTIME_VERSIONS();
    descriptor = [[GPBFileDescriptor alloc] initWithPackage:@"test"
                                                     syntax:GPBFileSyntaxProto3];
  }
  return descriptor;
}

#pragma mark - SearchRequest

@implementation SearchRequest

@dynamic query;
@dynamic pageNumber;
@dynamic resultPerPage;

typedef struct SearchRequest__storage_ {
  uint32_t _has_storage_[1];
  int32_t pageNumber;
  int32_t resultPerPage;
  NSString *query;
} SearchRequest__storage_;

// This method is threadsafe because it is initially called
// in +initialize for each subclass.
+ (GPBDescriptor *)descriptor {
  static GPBDescriptor *descriptor = nil;
  if (!descriptor) {
    static GPBMessageFieldDescription fields[] = {
      {
        .name = "query",
        .dataTypeSpecific.className = NULL,
        .number = SearchRequest_FieldNumber_Query,
        .hasIndex = 0,
        .offset = (uint32_t)offsetof(SearchRequest__storage_, query),
        .flags = GPBFieldOptional,
        .dataType = GPBDataTypeString,
      },
      {
        .name = "pageNumber",
        .dataTypeSpecific.className = NULL,
        .number = SearchRequest_FieldNumber_PageNumber,
        .hasIndex = 1,
        .offset = (uint32_t)offsetof(SearchRequest__storage_, pageNumber),
        .flags = GPBFieldOptional,
        .dataType = GPBDataTypeInt32,
      },
      {
        .name = "resultPerPage",
        .dataTypeSpecific.className = NULL,
        .number = SearchRequest_FieldNumber_ResultPerPage,
        .hasIndex = 2,
        .offset = (uint32_t)offsetof(SearchRequest__storage_, resultPerPage),
        .flags = GPBFieldOptional,
        .dataType = GPBDataTypeInt32,
      },
    };
    GPBDescriptor *localDescriptor =
        [GPBDescriptor allocDescriptorForClass:[SearchRequest class]
                                     rootClass:[SearchRequestRoot class]
                                          file:SearchRequestRoot_FileDescriptor()
                                        fields:fields
                                    fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription))
                                   storageSize:sizeof(SearchRequest__storage_)
                                         flags:GPBDescriptorInitializationFlag_None];
    NSAssert(descriptor == nil, @"Startup recursed!");
    descriptor = localDescriptor;
  }
  return descriptor;
}

@end


#pragma clang diagnostic pop

// @@protoc_insertion_point(global_scope)
```

これだけだと使えないので、iOSプロジェクトにProtobufのライブラリ群を追加する必要がある
自力でビルドしてプロジェクトに追加もできるが、
https://github.com/google/protobuf/tree/master/objectivec

が、CocoaPodsのが楽

##CocoapodsでiOSプロジェクトに追加
XCode->New Project->xxxx.xcodeprojと同じディレクトリに下記を追加

```pod:Podfile
target 'xxxx' do
  pod 'Protobuf'
end
```

##サーバーとの疎通を確認

###クライアント側
search_request.protoから生成したSearchRequest.pbobjc.hとSearchRequest.pbobjc.mをプロジェクトに追加
適当なURLにポストするサンプル

```objc:ViewController.m
//
//  ViewController.m
//  TestProtoBufPod
//
//  Created by 山中夏樹 on 2017/03/15.
//  Copyright © 2017年 山中夏樹. All rights reserved.
//

#import "ViewController.h"
#import "SearchRequest.pbobjc.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self request];
}


- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (SearchRequest*) getData {
    SearchRequest* data = [[SearchRequest alloc] init];
    data.query = @"hoge";
    data.pageNumber = 1;
    data.resultPerPage = 2;
    return data;

}

- (void) request {
    NSURL* url = [NSURL URLWithString:@"http://{リクエストしたいURL}/"];
    NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSURLSession* session = [NSURLSession sessionWithConfiguration:config];
    NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
    
    request.HTTPMethod = @"POST";
    request.HTTPBody = [[self getData] data];
    NSLog(@"SearchRequest -- %@", [[self getData] description]);//descriptionだけで中身見れる
    NSURLSessionDataTask* task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        //サーバーのレスポンスが文字列ならここで受け取る        
        NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"str -- %@", str);

        //サーバーのレスポンスがProtocolBuffersのSearchRequestだったらここで受け取る
        NSLog(@"parseFromData -- %@", [[SearchRequest parseFromData:data error:nil] description]);
    }];
    [task resume];
}

@end

```

###サーバー側

node.jsを使う想定で仮APIサーバーをたてる

[環境](#環境)

npmで必要なモジュールインストール
```
npm install connect
npm install protocol-buffers
```

仮サーバーたてるコードをObjectiveC側のコードを生成した元のsearch_request.protoファイルと同じ階層において、node.jsからシリアライズ、デシリアライズできるようにしておく

作業ディレクトリ
├search_request.proto
└server.js

受け取ったSearchRequestオブジェクトをデシリアライズしてログ出力し、適当に生成したSearchRequestオブジェクトをレスポンスで返すだけの仮APIサーバー:server.js

```js:server.js
var fs = require('fs')
var protobuf = require('protocol-buffers')
var messages = protobuf(fs.readFileSync('search_request.proto'))

require('connect')().use(function(req, res, next) {
	next();
}).use(function(req, res) {
	//Objective Cからのリクエストをシリアライズ
	var body='';
	req.on('data', function (data) {
		body +=data;
	});
	req.on('end',function(){
		try {
			//デシリアライズしてログに出力
			var obj = messages.SearchRequest.decode(body)
			console.log(obj)
		} catch (e) {
			console.log(e)
		}
		res.writeHead(200, {'Content-Type': 'application/octet-stream'});
		//適当なメッセージ生成
		var buf = messages.SearchRequest.encode({
		  query: "hoge",
		  page_number: 1234
		})
		res.write(buf, 'binary');
		//メッセージをレスポンス
		res.end();
	});
  }).listen(3000);
```

サーバー起動しておく(Ctl+Cで切れるので、起動しながらiOS側実行する)

```bash
node server.js
```

###実行結果確認

ローカルならさっきのViewController.mの{リクエストしたいURL}に127.0.01:3000と入れてリクエストすると、node側にリクエストが届いてProtocolBuffersのやり取りができているのが分かるはず

以上

##エラーメモ

``objectivec/DevTools/full_mac_build.sh``が通らなくてハマる人がいたらここら辺

```bash

#下記エラー
"+ autoreconf -f -i -Wall,no-obsolete"
"Can't exec "aclocal": No such file or directory at /usr/local/Cellar/autoconf/2.69/share/autoconf/Autom4te/FileUtils.pm line 326.
autoreconf: failed to run aclocal: No such file or directory"

#autoreconfがないのでインストール
$ brew install autoconf

"Please delete these paths and run `brew update`.
Warning: autoconf-2.69 already installed, it's just not linked."

#インストール済み？

#Can't exec "aclocal": -> automakeがないらしい
#http://qiita.com/DQNEO/items/11d6056547cab0632901
#インストール
$ brew instal automake

↓

"Error: You must `brew link autoconf` before automake can be installed"
$ brew link autoconf
#再度インストール
$ brew instal automake
#成功したら再度ビルド実行
$ objectivec/DevTools/full_mac_build.sh

"configure.ac:30: error: possibly undefined macro: AC_PROG_LIBTOOL
      If this token and others are legitimate, please use m4_pattern_allow.
      See the Autoconf documentation."

#libtoolも要るらしいのでインストール
$ brew install libtool
#再度ビルド実行
$ objectivec/DevTools/full_mac_build.sh

```