#環境
- 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プロジェクトに追加
サーバーとの疎通を確認
エラーメモ
#参考
公式
https://github.com/google/protobuf
公式 - Objective C
https://github.com/google/protobuf/tree/master/objectivec
##クラス生成手順
#先に必要なものインストール
$ brew install libtool
$ brew install autoconf
$ brew install automake
$ brew install protobuf
#インストール終了
$ protoc --version
libprotoc 3.2.0
###レポジトリ落としてきてビルドする場合
$ 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ファイルを作る
$ cd {どこか適当な場所}
$ touch search_request.proto
$ vim 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={生成先ディレクトリ}オプションで作る
$ 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
下記のようなファイルが出来上がった
// 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)
// 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と同じディレクトリに下記を追加
target 'xxxx' do
pod 'Protobuf'
end
##サーバーとの疎通を確認
###クライアント側
search_request.protoから生成したSearchRequest.pbobjc.hとSearchRequest.pbobjc.mをプロジェクトに追加
適当なURLにポストするサンプル
//
// 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
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側実行する)
node server.js
###実行結果確認
ローカルならさっきのViewController.mの{リクエストしたいURL}に127.0.01:3000と入れてリクエストすると、node側にリクエストが届いてProtocolBuffersのやり取りができているのが分かるはず
以上
##エラーメモ
objectivec/DevTools/full_mac_build.sh
が通らなくてハマる人がいたらここら辺
#下記エラー
"+ 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