This article is about how to integrate the compilation of protobuf files into Xcode, so as to directly compile and run.proto files in Xcode just like adding normal OC files without any extra operations.

Awesome. How smart is that? Yes, it is!

The author’s company now uses a unified set of Protobuf data structure for all ends, which eliminates the repetitive work of multiple ends repeatedly defining the same set of data structure, which is highly efficient and highly recommended. Some minor optimizations have been made in Xcode 10 to add support for Protobuf, and in the near future Xcode support for Protobuf will be even smarter!

As for what is Protobuf and Protobuf syntax tutorial, which is not the subject of this article, please Google it.

Environment: Xcode 10+ language: Objective-C

Without further ado, the subject begins:

First of all, a true enterprise project is not just one or two.proto files demonstrated in many tutorials on the Internet, but a collection of.proto files and directories, and they are multi-shared. You’ll find that you can follow the tutorials and write a demo, but when it comes to enterprise level use, it’s not enough. You’ll run into all kinds of pitfalls. Don’t ask me how I know it. I’ve got it all figured out by myself.

Installing the Compilation tool

First, to be able to compile a Protobuf file, we need to install an official compiler. You can choose any of the following installation methods you prefer:

  1. Source code compilation installation; Github.com/protocolbuf…
  2. Directly download the compiled binary file of the corresponding language version; Github.com/protocolbuf…
  3. Use the brew;brew install protobuf;

After the installation is complete, enter which protoc in terminal to check whether the installation is successful. If the installation is successful, /usr/local/bin/protoc is displayed

If you have any questions, please Google them. They are not covered by this tutorial.

Integrate the Protobuf library into the Xcode project

There’s nothing to talk about, just create a new Xcode project. Import Protobuf libraries using Cocoapods:

Pod search Protobuf

Just choose the most stable version.

Pit # 1: Note that the compiler and the version of the Protobuf Framework introduced by the Pod need to correspond. If your build tool is 3.9.0, then the Protobuf version should also be 3.9.0. If the Pod Protobuf library is updated later, the compiler will need to follow suit. Inconsistent versions may cause compilation errors at runtime.

Create the.proto file

  1. [Fixed] Create a Protos directory in a new project

Real enterprise projects are not just one or two.proto files like many tutorials on the web. Depending on the module used, there will be different folders, or even the entire root directory storing.proto files will be stored as a Git submodule to achieve the purpose of multi-terminal sharing. The directory level of the Proto source file, which has a huge impact on compilation results, is directly related to use in Xcode, and this is the biggest pit, which we’ll talk about later;

  1. Create two more subdirectories under the Protos root, representing different modules in the actual project. For easy memory, one is a directory and the other is B directory;

  2. Create the A.proto source file in the a directory. Create b. proto file in directory B;

There are two ways to create a.proto file:

  • Create it from the command line, and then drag it to the Xcode project.
  • Go directly to Xcode by right-clicking A directory and selectingNew FileAnd then choose in turniOS --> Other --> Empty, add the suffix.proto to the file name.

The file name format of proto must be big hump. Be sure to start with a capital letter. Because even if the file names are all lowercase, the result will be a big camel name file. For example, test.proto compacts test. pbobjc.h and test. pbobjc.m files;

As for the contents of the file, if you are familiar with the Protobuf syntax, just write a few lines, if not, copy the contents of my test:

A. Proto File contents:

syntax = "proto3";

import "b/b.proto"; // Insert the b/b.proto file in the A.proto file, be sure to specify the path ~ option objc_class_prefix ="PXL";

package a; 

message TestA {
    string name = 1;
    b.TestB test = 2;
}
Copy the code

B. Proto File contents:

syntax = "proto3";

option objc_class_prefix = "PXL";

package b;

message TestB {
    string name = 1;
}

Copy the code

Pit point three: Note that it is created either way. In versions prior to Xcode10, after creating the files, go to Project –> Build Phases –> Compile Sources and add the new a.proto and b.proto files. What does that mean? This means adding these two files to the compilable file. Only compilable files can be customized for subsequent compilation; Not Xcode10, Xcode10 already has some specific optimizations for Protobuf.

Add custom build scripts for the project

Xcode itself does not recognize.proto files, so it does not automatically compile them. We need to integrate the.proto compiler itself into the project as follows:

  1. Enter the following directories in turn:

Project –> Build Rules –> Click the + sign to generate a specific file type to compile the script.

  1. Select Protobuf Source Files in Process; (note that if versions prior to Xcode10 don’t have this option, you need to select Source files with names matching and type *.proto in the box below);

  2. Follow the official tutorial to add the compile script:

/usr/local/bin/protoc --proto_path=${SRCROOT}/< your project directory name >/protos/ --objc_out=${DERIVED_FILE_DIR} $INPUT_FILE_PATH 

Copy the code

Such as:

/usr/local/bin/protoc --proto_path=${SRCROOT}/ProtoTests/protos/ --objc_out=${DERIVED_FILE_DIR} $INPUT_FILE_PATH
Copy the code

Here are a few things to note:

  1. Protoc commands try to specify absolute paths in case the command cannot be found during script compilation. That is /usr/local/bin/protoc instead of protoc. This point is not mentioned in the official documentation, but is a pit we encountered ourselves;

  2. Here are a few environment variables to use:

    ${SRCROOT} is the Xcode environment variable, representing the project root directory;

    ${INPUT_FILE_PATH} represents the absolute input path of the script execution file, including the file name itself, and the file format.

    ${INPUT_FILE_BASE} indicates the file name of the script execution file, excluding the suffix format.

    ${INPUT_FILE_NAME} indicates the name of the script execution file, including the suffix format.

    ${DERIVED_FILE_DIR} represents the Xcode file output directory;

    Other Xcode cabin environment variables at https://gist.github.com/gdavis/6670468. Of course, you can also view it in the project Build log.

  3. As documented, –proto_path corresponds to the absolute root of the proto source file. –objc_out is the directory where files generated by compilation are stored.

why--proto_pathNeed to be the absolute root?

Let’s try replacing –proto_path with relative path, and see what happens, which is the script, okay

cd ${SRCROOT}/ProtoTests/protos/
/usr/local/bin/protoc --proto_path=./ --objc_out=${DERIVED_FILE_DIR} $INPUT_FILE_PATH
Copy the code

Compile run, eee ~ error. Viewing the log, we can see the following log message:

File does not reside within any path specified using --proto_path (or -I).  You must specify a --proto_path which encompasses this file.  Note that the proto_path must be an exact prefix of the .proto file names -- protoc is too dumb to figure out when two paths (e.g. absolute and relative) are equivalent (it's harder than you think).
Copy the code

In –proto_path you must specify the exact path of the source file, protoc is too dumb to figure out if the relative path is the absolute path we want. Google engineers say it’s too hard. The –proto_path parameter must be the absolute path to the root of the proto file.

Then why are we using it$INPUT_FILE_PATH?

As stated above, ${INPUT_FILE_PATH} is the absolute path to the compiled input source file.

Protoc –proto_path= SRC –objc_out=build/gen SRC /foo.proto SRC /bar/baz.proto

What does that mean?

It says that eventually the compiler will compile SRC /foo.proto files into: build/gen/ foo.pbobjc.h and build/gen/ foo.pbobjc.m files. And the SRC/bar/baz. Proto file compiled into the build/gen/bar/baz pbobjc. H and build/gen/bar/baz pbobjc. M. Instead of build/gen/ baz.pbobjc.h and build/ Gen/baz.pbobjC.m

This means that the protobuf compiler will automatically generate files in the file source directory structure.

It is important to note that the build/ Gen directory will not be created automatically, it will need to be built in advance.

Also, if you look at the.m files that are generated by the final compilation, you’ll see some interesting things; For example, if I add a b. proto file to a. proto, you’ll see that Protobuf’s final compiled a.bobjc. m import file format contains the file path, for example:

import "a/A.pbobjc.h"
import "b/B.pbobjc.h"
Copy the code

Set the output path of the compiled file

Note that the output path of the proto file set above is $DERIVED_FILE_DIR.

The answer is to facilitate integration with Xcode.

For custom compiled scripts, you need to set the output path of a file.

We click the + sign under Output Files under the script box to specify the file Output path. Since OC files are divided into dot h and dot m files, we specify two.

And when you do, you’ll find, Xcode defaults to $(DERIVED_FILE_DIR)/newOutputFile, $(DERIVED_FILE_DIR)/${INPUT_FILE_BASE}.pbobjc.h and $(DERIVED_FILE_DIR)/${INPUT_FILE_BASE}.pbobjC. m, -fno-objc-arc in the. M file Compiler Flags indicates that the. M file is compiled using MRC.

Compile run, done, is impossible !!!!

You’ll notice another error:

clang: error: no such file or directory: '~/Library/Developer/Xcode/DerivedData/ProtoTests-dpojqcqwplnmyzbgdvjiqjfefgky/Build/Intermediates.noindex/ProtoTests.bu ild/Debug-iphonesimulator/ProtoTests.build/DerivedSources/A.pbobjc.m'
Copy the code

What does that mean? The A.p Bob jc.m file is not found in DerivedSources. Because we specified that the output path of this compilation is in this directory, Xcode will look in this directory when compiling the OC file, but it won’t find it. Why can’t I find it? A/a.bobjc.m is a/ a.bobjc.m. The reason we already mentioned is that protoc final compilations are automatically prefixed with directories.

One might say, could you change the output file to $(DERIVED_FILE_DIR)/*/${INPUT_FILE_BASE}.pbobjC.h? So let’s give it a try.

Compile operation

what the hell?

clang: error: no such file or directory: '~/Library/Developer/Xcode/DerivedData/ProtoTests-dpojqcqwplnmyzbgdvjiqjfefgky/Build/Intermediates.noindex/ProtoTests.bu ild/Debug-iphonesimulator/ProtoTests.build/DerivedSources/*/A.pbobjc.m'
Copy the code

Xcode’s Output Files are so stupid that they don’t support wildcards like $(DERIVED_FILE_DIR)/*/${INPUT_FILE_BASE}.pbobjC.h. There is also no support for passing in any custom variables.

Only explicit file paths and Xcode environment variables, but in real projects, there may be more than one level of path, there may be nested folders under folders.

Oh, shit. What are we gonna do about this?

When we were about to give up, we consulted our script guru. We tried adding the following two lines to the end of the script:

# cd ${DERIVED_FILE_DIR}
# find . -mindepth 2 -name ${INPUT_FILE_BASE}.pbobjc.m -o -name ${INPUT_FILE_BASE}.pbobjc.h | xargs -I{} cp "{}" .
Copy the code

Isn’t that clever?

What does that mean? This means that we CD to the directory, then find the corresponding generated OC file, and copy it to the root directory. With the will of praying for god, run the following, Perfect, finally no longer error, to the directory to check, which is exactly what we want, all files have been copied out.

The next step is to import and use it normally in the project.

Use it

You think this is the end of it? There are pits here. There are two things to note:

  1. When we import these generated OC files, if you are using Xcode’s new build system, you should use #import < b.bobjc.h > when importing. You will find that #import “b.bobjc.h” also works. But Xcode doesn’t give you a hint. What to do? Just set Xcode to the old build system. Setup method: File –> Workspace Settings. Change New Build System to Legacy Build System. Confidentially, this setting will solve the problem that Xcode does not prompt you to import files that are not Protobuf compiled

  2. Import is done by selecting #import “b.bobjc.h” or #import “b/ b.bobjC.h “. It is recommended to use directory if you like, both because Protobuf generated files do this, and because xcode output file directories will be more intelligent in the future.

Okay, so that’s it. If you don’t understand the article very well, you need a demo. Or if you have a better idea, leave it in the comments

AFNetworking can request Protobuf data directly from the back end