Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

環境

  • 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
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

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

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_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と同じディレクトリに下記を追加

Podfile
target 'xxxx' do
  pod 'Protobuf'
end

サーバーとの疎通を確認

クライアント側

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

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away