Google protocol buffers on iOS

2019-01-16 08:34发布

问题:

Is the metasyntactic static library for iOS . . .

http://code.google.com/p/metasyntactic/wiki/ProtocolBuffers

. . . compatible with regular old C++ compiled protofiles? I do not want to use the bundled compiler that generates Obj-C.

Is there any way to compile the library supplied by Google for iOS?

回答1:

Ok. It appears that the metasyntactic library (or any other 3rd party library) is unnecessary in this case. You can just add the Google source directly to your project. I found the following answer from Nicola Ferruzzi in a google discussion group . . .

The original answer is here . . .

http://groups.google.com/group/protobuf/browse_thread/thread/ca4218d7db144252

The content of this answer is included below with images to make a permanent record ...


EDIT

Since trying this again tonight for the first time in a while, I needed a couple more steps in addition to those outlined below (this works with protobuf 2.5.0).

  • You'll need to link against libz.dylib. You set this in Build Phases > Link Binary With Libraries.
  • To easily remove all of the unit test related stuff, use the following command from the shell in the google directory find . -name "*unittest*" -exec rm -rf {} \;
  • Also delete the folder called testing
  • comment out #include <google/protobuf/testing/googletest.h> in stringprintf.cc
  • Now carefully follow the instructions below and all should work fine.

I use latest version in my app .. you don't really need objc direct support if you are familiar with C++, there is only one point where you have to pass from std::string to NSData and viceversa. And its pretty simple.

To compile and test the easiest way Ive found is to just import the whole google directory in my own project :) (the second time you can make your own framework but for testing this procedure just works)

  • download latest version
  • autogen configure and make like you were just building for macosx (you need command line tools) . This way you end up with protoc
    binary and the library for macosx (which you don't need)
  • open your Xcode iOS project
  • add "new file" to your project and select google directory
  • add the directory of google headers to your additional include directories
  • add config.h from the protobuffer src directory to your app
  • from the google group remove everything that contains unitest :)
  • from the google group remove compiler and java stuff;

You should be able to compile without any linking error. To give you an idea this is what I directly compile

Then you can use protoc to generate c++ source files for your protocol. To use them with objc you have to rename your source to file "mm" then you can do something like

TO SERIALIZE TO NSDATA

let's say your message is called Packet

- (NSData *)getDataForPacket:(Packet *)packet { 
    std::string ps = packet->SerializeAsString(); 
    return [NSData dataWithBytes:ps.c_str() length:ps.size()]; 

TO READ FROM NSDATA

- (Packet *)getPacketFromNSData:(NSData *)data { 
  char raw[[data length]]; 
  Packet *p = new Packet; 
  [data getBytes:raw length:[data length]]; 
  p->ParseFromArray(raw, [data length]); 
  return p; 

}


回答2:

You can add support for Google Protocol Buffers to an Xcode 5 project using Cocoapods by adding the following line to your Podfile.

pod 'GoogleProtobuf', '~> 2.5.0'

This will place the C++ version of the protobuf code into a Pod for your project. It will also add the protoc compiler in the folder Pods/GoogleProtobuf/bin/protoc within your project.

You can create a custom build rule in your project that automatically converts the .proto files into .ph.{h,cc} files. Here is how I did that:

Setup a build rule to "Process Source files with names matching: *.proto Using Custom Script". The script should include the following:

cd ${INPUT_FILE_DIR}
${SRCROOT}/Pods/GoogleProtobuf/bin/protoc --proto_path=${INPUT_FILE_DIR} ${INPUT_FILE_PATH} --cpp_out=${INPUT_FILE_DIR}/cpp

Set the output files to include the following:

$(INPUT_FILE_DIR)/cpp/$(INPUT_FILE_BASE).pb.h
$(INPUT_FILE_DIR)/cpp/$(INPUT_FILE_BASE).pb.cc

Any .proto files you include in your project will now automatically be converted to C++ and then compiled as part of your build.



回答3:

EDIT : I had answered this earlier but was deleted by moderator. So I have included some code from the tutorial.

A tutorial which is almost same as the answer posted above - Using Google Protocol Buffers in Objective-C on iOS and the Mac

Follow the steps given in learnvst's answer, and refer the comments for pitfalls. I followed the exact same steps except for

add the directory of google headers to your additional include directories I added the src/ directory in the header search paths, instead of the google directory.

Also, when i did #import xyz.pb.h the project wasn't building. When I renamed my .m file to .mm i was able to build. This point is mentioned in the tutorial very subtly :P.

Basically, any .m file which is importing a any .pb.h file, should be renamed with extension .mm

Here's some content from the tutorial -

PROTO FILE

package kotancode;

enum ZombieType {
    SLOW = 0;
    FAST = 1;
}

message ZombieSighting {
    required string name = 1;
    required double longitude = 2;
    required double latitude = 3;
    optional string description = 4;
    required ZombieType zombieType = 5 [default = SLOW];
}

ZombieSightingMessage.h

// -- ZombieSightingMessage.h - note my C++ object is not in the public interface.
#import <Foundation/Foundation.h>

@interface ZombieSightingMessage : NSObject
- (void)doSomething;
@end

ZombieSightingMessage.mm

// -- ZombieSightingMessage.mm
#import <UIKit/UIKit.h>
#import "ZombieSightingMessage.h"
#import "zombie.pb.h"

@implementation ZombieSightingMessage

- (void)doSomething {
    // Doing random stuff with a UIView here to show the mixing
    // of C++ and Objective-C/Cocoa syntax in the same file...
    UIView *uiView = [[UIView alloc] init];
    [uiView setCenter:CGPointMake(20, 10)];

    // instantiate my protobuf-generated C++ class.
    kotancode::ZombieSighting *zombieSighting = new kotancode::ZombieSighting();
    zombieSighting->set_name("Kevin");
    zombieSighting->set_description("This is a zombie");
    zombieSighting->set_latitude(41.007);
    zombieSighting->set_longitude(21.007);
    zombieSighting->set_zombietype(kotancode::ZombieType::FAST);

    // Some small tomfoolery required to go from C++ std::string to NSString.
    std::string x = zombieSighting->DebugString();
    NSString *output = [NSString stringWithCString:x.c_str() encoding:[NSString defaultCStringEncoding]];
    NSLog(@"zombie: %@", output);

    // Instantiate another zombie from the previous zombie's raw bytes.
    NSData *rawZombie = [self getDataForZombie:zombieSighting];
    kotancode::ZombieSighting *otherZombie = [self getZombieFromData:rawZombie];

    // Dump the second zombie so we can see they match identically...
    NSString *newOutput = [NSString stringWithCString:otherZombie->DebugString().c_str() encoding:[NSString defaultCStringEncoding]];
    NSLog(@"other zombie: %@", newOutput);

    // Grimace all you want, but this is C++ and we need to clean up after ourselves.
    free(zombieSighting);
    free(otherZombie);

}

// Serialize to NSData. Note this is convenient because
// we can write NSData to things like sockets...
- (NSData *)getDataForZombie:(kotancode::ZombieSighting *)zombie {
    std::string ps = zombie->SerializeAsString();
    return [NSData dataWithBytes:ps.c_str() length:ps.size()];
}

// De-serialize a zombie from an NSData object.
- (kotancode::ZombieSighting *)getZombieFromData:(NSData *)data {
    int len = [data length];
    char raw[len];
    kotancode::ZombieSighting *zombie = new kotancode::ZombieSighting;
    [data getBytes:raw length:len];
    zombie->ParseFromArray(raw, len);
    return zombie;
}

@end

EDIT : I am using Xcode 4.5. Even after I followed all the steps I was getting a linker error.

symbols not found for architecture i386

Due to this I couldnt run the code on simulator. But it worked on actual device



回答4:

I guess based on the actual question my comment is worth posting as an answer:

I'm using a slightly modified version of the native Obj code generation provided by Booyah

It supports repeated fields out of the box but in order to use ObjC fast enumeration you need to convert the PBArray type (basically a typed c buffer) to an array of NSObjects that it represents - either the NSNumber or protobuf message of the objects. You can see an example of the updated fast enumeration code in this change: . You could also add a category for that on PBArray called toObjects.

I just mark the generated code with -fno-objc-arc, but you can get arc and 2.5 support from the booyah pull requests.

The directions are pretty good for setup, but if people want more explicit instructions on the category I use, how I built the protobuf-objc plugin, how to get support for Class prefixes (e.g. IXMyProtoMessage instead of MyProtoMessage) or how I generate the code let me know and I'll try to set aside time to write up a post. I'm using it with > 50 proto files with a lot of cross project dependencies.

A weakness of the library is that it doesn't include the typical Protobuf reflection api on the generated code so doing something like converting a message to a NSDictionary would have to do some hacky stuff with the objC runtime (the code doesn't follow typical KV compliance) or write a custom code generator from protos that do have the reflection api (I did this with python + jinja2). Or - better yet and of similar difficulty, add reflection apis to the code generator ;).