Translated from:Introduction to Protocol Buffers on iOS

For most applications, backend services, transmission and storage of data are important modules. When writing an interface to a Web service, developers typically use JSON or XML to send and receive data, and then generate and parse the structure from that data.

There are plenty of apis and frameworks to serialize and deserialize, though, to support the routine work of back-end interface development, such as updating code or parsers to support model changes in the background.

But if you really want to improve the robustness of your new project, consider using Protocol Buffers, a cross-language method developed by Google for serializing data structures. In many cases, it is more flexible and effective than traditional JSON and XML. One of the key features is that you only need to define the data structure once, in whatever language and compiler it supports — including Swift! Create class files that can be easily read and written into objects.

In this tutorial, you’ll use a Python server to interact with an iOS application. You’ll learn how Protocol Buffers work, how to configure the environment, and finally how to use Protocol Buffers to transfer data.

What, still not convinced that protocol buffers are what you need? Read on.

Note: This tutorial is based on the fact that you already have some experience with iOS and Swift, as well as some basic server and Terminal basics. Also, make sure you’re using Apple’s Xcode 8.2 or later.

RWCards this APP can be used to view your conference tickets and speaker list. Download Starter Project and open the root directory Starter. #####The Client in Starter/RWCards, open rwcards.xcworkspace, let’s take a look at The main files: Starter/RWCards

  • SpeakersListViewController. Swift managed a table used to show the speaker list view. This controller is still just a template because you haven’t created a model for it yet.
  • SpeakersViewModel. Swift is equivalent to SpeakersListViewController data source, it will contain the speaker list data.
  • Cardviewcontroller.swift is used to display an attendee’s business card and his social information.
  • Rwservice. swift manages the interaction between the client and the backend. You might use Alamofire to initiate a service request.
  • Main.storyboard The storyboard for the entire APP.

The entire project uses CocoaPods to pull these two frames:

  • Swift Protobuf supports the use of Protocol Buffers in Xcode.
  • Alamofire an HTTP network library that you will use to request a server.

Note: In this tutorial you will use Swift Protobuf 0.9.24 and Google’s Protoc Compiler 3.1.0. They’re already packaged in the project, so you don’t need to do anything else.

How does Protocol Buffers work?

Before you can start using protocol buffers, define a.proto file. Specify your data structure information in this file. Here is an example of a.proto file:

syntax = "proto3";
 
message Contact {
 
  enum ContactType {
    SPEAKER = 0;
    ATTENDANT = 1;
    VOLUNTEER = 2;
  }
 
  string first_name = 1;
  string last_name = 2;
  string twitter_name = 3;
  string email = 4;
  string github_link = 5;
  ContactType type = 6;
  string imageName = 7;
};
Copy the code

This file defines a Contact message and its associated properties.

Once the.proto file is defined, all you need to do is hand it over to the Protocol Buffer compiler, which creates a data class (structure in Swift) in the language of your choice. You can use this class/structure directly in your project, very simple!

.proto

advantage

JSON and XML are probably the standard solutions developers use today to store and transfer data, and Protocol Buffers have the following advantages over them:

  • Fast and small: According to Google, Protocol Buffers are 3-10 times smaller and 20-100 times faster than XML. In this article, written by Damien Bod, we compare the read and write speeds of some major text formats.
  • Type-safe: Protocol buffers, like Swift, are type-safe, and with Protocol buffers you need to specify the type of each attribute.
  • Automatic deserialization: you don’t need to write any parsing code, just update the.proto file. file and regenerate the data access classes.
  • Sharing is caring: With support for multiple languages, data models can be shared across different platforms, which means cross-platform work is easier.

limitations

Protocol Buffers have many advantages, but they are not a panacea:

  • Time cost: Using Protocol Buffers may not be efficient in older projects because of the conversion cost. Project members also need to learn a new grammar.
  • Readability: XML and JSON are more descriptive and easy to read. The raw data for Protocol buffers cannot be read and cannot be parsed without the.proto file.
  • Just not: XML is the best choice when you want to use stylesheets like XSLT. So protocol buffers are not always the best tool.
  • Not supported: The compiler may not support the language or platform you are working on.

Although not suitable for all situations, Protocol Buffers do have a number of advantages. Let’s try running the program.

Protocol Buffer template

Head back to Finder and look inside Starter/ProtoSchema. You’ll see the following files: Open the Starter/ProtoSchema directory and you’ll see these files:

  • Contact. proto defines a contact structure using the syntax of the protocol buffer. We’ll talk more about that later.
  • The protoscript.sh bash script uses the protocol buffer compiler to read contact.proto to generate Swift and Python data models, respectively.
The service side

The Starter/Server directory contains:

  • Rwserver. py is a Python service placed on Flask. Contains two GET requests:

    • /currentUser Gets information about current participants.
    • / Speakers Get the speaker list.
  • Rwdict.py contains the speaker list data that RWServer will read.

It is now time to configure the environment to run Protocol Buffers. In the following sections, you’ll create the Environment to run Google’s Protocol Buffer compiler, Swift’s Protobuf plugin, and install Flask to run your Python service.

Environment configuration

A number of tools and libraries need to be installed before protocol Buffers can be used. The Starter project includes a script called ProtoInstallation.sh that does this for you. It checks to see if these libraries are already installed before installation. This script takes a little time to install, especially Google’s Protocol Buffer library. Open your terminal, CD to the Starter directory and execute the following command:

$ ./protoInstallation.sh
Copy the code

Note: You may be asked to enter the administrator password during this process.

After executing the script, run it again to ensure the following output:

If you see this, the script has been executed. If the script fails, check to see if you entered the wrong administrator password. And rerun the script; It does not reinstall libraries that have already been successful. This script does these things:

  1. Install Flask to run Python native services.
  2. Generate the Protocol Buffer compiler from the Starter/ Protobuf-3.1.0 directory.
  3. Install the Protocol Buffer Python module so that the server can use the Protobuf library.
  4. Swift Protobuf pluginprotoc-gen-swiftMoved to/usr/local/bin. Enables the Protobuf compiler to generate Swift structures.

Note: You can use the editor to open the ProtoInstallation. sh file to see how this script works. This requires some bash foundation.

Ok, you have now done all the preparation for using Protocol buffers.

Define a.proto file

The.proto file defines the protocol buffer messages that describe your data structure. After passing the contents of this file to the Protocol Buffer compiler, the compiler will generate your data structure.

Note: In this tutorial, you will define messages using Proto3, the latest version of the Protocol Buffer language. Visit Google’s Guidelines for more information on Proto3.

Opened by the editor of your most used ProtoSchema/contact proto, it has defined the speaker’s message:

syntax = "proto3";
 
message Contact { // 1
 
  enum ContactType { // 2
    SPEAKER = 0;
    ATTENDANT = 1;
    VOLUNTEER = 2;
  }
 
  string first_name = 1; //3
  string last_name = 2;
  string twitter_name = 3;
  string email = 4;
  string github_link = 5;
  ContactType type = 6;
  string imageName = 7;
};
 
message Speakers { // 4
  repeated Contact contacts = 1;
};
Copy the code

Let’s take a look at what’s in it:

The Contact model describes a person’s Contact information. This will be displayed on their displays in The app.

  1. The Contact model is used to describe business card information. I’m going to display it on the badges page in the app.
  2. Each contact should be classified so that it is a visitor or a speaker.
  3. Each entry in the proto filemessageenum Must beSpecifies an incremental and uniquedigitalThe label. These figures are used for distinctionBinary format of informationtheThe domainThis is very important. accessreserved fieldsYou can learn more aboutThe labelandThe domainThe information.
  4. The Speakers model contains a collection of contacts, with * repeated* keywords representing an array of objects.

Proto is passed to the protoc program. The message in proto file will be converted to the Swift structure. These structures follow protobufmessage. protoc and provide ways to construct, serialize, and deserialize data in Swift.

Note: To learn more about Swift’s Protobuf API, visit Apple’s Protobuf API documentation.

In terminal, go to the Starter/ProtoSchema directory, open protoscript.sh with an editor, and you’ll see:

#! /bin/bash
echo 'Running ProtoBuf Compiler to convert .proto schema to Swift'
protoc --swift_out=. contact.proto // 1
echo 'Running Protobuf Compiler to convert .proto schema to Python'
protoc -I=. --python_out=. ./contact.proto // 2
Copy the code

This script executes protoc commands twice on the contact.proto file, creating Swift and Python source files respectively. Return to the terminal and execute the following command:

$ ./protoScript.sh
Copy the code

You should see the following output:

Running ProtoBuf Compiler to convert .proto schema to Swift
protoc-gen-swift: Generating Swift for contact.proto
Running Protobuf Compiler to convert .proto schema to Python
Copy the code

You have created the Swift and Python source files. In the ** ProtoSchema** directory, you will see a Swift and a Python file. There are also corresponding.pb.swift and.pb.py. Pb prefixes to indicate that these are classes generated by the Protocol buffer.

Drag contact.pb.swift to the Protocol Buffer Objects group under Xcode’s Project Navigator. Check the “Copy items if needed” option. Copy contact_pb2.py to the Starter/Server directory. Take a look at ** contact.pb.swift** and contact_pb2.py to see how proto message is translated into the target language. Now that you have generated model objects, you can start integrating! ## Run local server sample code contains a Python service. The service provides two GET requests: one to GET attendees’ name tags and one to list speakers. This tutorial does not delve into server-side code. However, you need to be aware that it uses the contact_pb2.py model file generated by the Protocol Buffer compiler. If you’re interested, take a look at the code in rwServer.py, or don’t (manual funny). Open the terminal and CD to the Starter/Server directory, and run the following command:

$ python RWServer.py

Copy the code

The running results are as follows:

Testing GET requests

By making an HTTP request in the browser, you can see the original data of the Protocol buffer. Open http://127.0.0.1:5000/currentUser you will see in your browser:

Try the speaker’s interface, http://127.0.0.1:5000/speakers:

Note: While testing the RWCards app you can exit, stop, and restart the local service for debugging purposes.

Now that you have the local server running, it uses the model generated by the proto file. Isn’t that cooool?

Initiating a service request

Now that you have your local server running, it’s time to make a service request in your app. ** rwservice. swift ** replaces the RWService class with the following code:

class RWService {
  static let shared = RWService() // 1
  let url = "http://127.0.0.1:5000"
 
  private init() { } func getCurrentUser(_ completion: @escaping (Contact?) -> ()) {// 2let path = "/currentUser"
    Alamofire.request("\(url)\(path)").responseData { response in
      if let data = response.result.value { // 3
        let contact = try? Contact(protobuf: data) // 4
        completion(contact)
      }
      completion(nil)
    }
  }
}
Copy the code

This class will be used to interact with your Python server. You have implemented the request to get the current user:

  1. Shared is a singleton that initiates a network request.
  2. The getCurrentUser(_:) method passes/currentUserThe path initiates a network request for user information, and a hard-coded user information is returned in the background.
  3. The if let gets the data.
  4. Data contains the protocol Buffer binary data returned by the server. The constructor for Contact takes data as an input parameter and decodes the data.

Decoding the data simply requires passing the protocol buffer data to the object’s constructor, and no additional parsing is required. Swift’s Protocol Buffer library takes care of everything for you. Now that the request is complete, you can present the data.

Integrate attendees’ business cards

Open the cardViewController.swift file and add the following code after viewWillAppear(_:) :

func fetchCurrentUser() { // 1
  RWService.shared.getCurrentUser { contact in
    if let contact = contact {
      self.configure(contact)
    }
  }
}
 
func configure(_ contact: Contact) { // 2
  self.attendeeNameLabel.attributedText = NSAttributedString.attributedString(for: contact.firstName, and: contact.lastName)
  self.twitterLabel.text = contact.twitterName
  self.emailLabel.text = contact.email
  self.githubLabel.text = contact.githubLink
  self.profileImageView.image = UIImage(named: contact.imageName)
}
Copy the code

These methods will help you get the data from the server and configure the business card:

  1. FetchCurrentUser () asks the server to get information about the current user and use * Contact * to configure * CardViewController*.
  2. Configure (_:) configures the UI with the contact passed in.

It’s easy to use, but you need to get a ContactType enumeration to distinguish the types of participants.

User-defined Protocol Buffer objects

You need to add a method to convert the enumeration type to string so that the business card page displays SPEAKER instead of a number 0. But there’s a problem. How do you add new functionality to the model without regenerating the.proto file to update message?

contact+extension.swift
Protocol Buffer Objects

extension Contact {
  func contactTypeToString() -> String {
    switch type {
    case .speaker:
      return "SPEAKER"
    case .attendant:
      return "ATTENDEE"
    case .volunteer:
      return "VOLUNTEER"
    default:
      return "UNKNOWN"}}}Copy the code

The contactTypeToString() method maps the ContactType to a corresponding display string. Open cardViewController.swift and add the following code to configure(_:) :

self.attendeeTypeLabel.text = contact.contactTypeToString()

Copy the code

The string representing contact Type is passed to * attendeeTypeLabel*. Finally in the viewWillAppear (_), applyBusinessCardAppearance () then add the following code:

if isCurrentUser {
  fetchCurrentUser()
} else {
  // TODO: handle speaker
}
Copy the code
  • IsCurrentUser * has been hardcoded to true, which will be changed when set to speaker. The *fetchCurrentUser() * method is called by default to get the business card information and populate it with the business card. Run the program to see the attendees’ business card page:

Integrated Speaker list

Once My Badge TAB is complete, let’s take a look at the Speakers TAB. Open rwService. swift and add the following code:

func getSpeakers(_ completion: @escaping (Speakers?) -> ()) {// 1let path = "/speakers"
  Alamofire.request("\(url)\(path)").responseData { response in
    if let data = response.result.value { // 2
      let speakers = try? Speakers(protobuf: data) // 3
      completion(speakers)
    }
  }
  completion(nil)
}
Copy the code

Looks familiar, right? It’s similar to getCurrentUser(_:) except that it gets the Speakers object, which contains an array of contacts to represent recalled Speakers. Open SpeakersViewModel.swift and replace the code with:

class SpeakersViewModel {
  var speakers: Speakers!
  var selectedSpeaker: Contact?
 
  init(speakers: Speakers) {
    self.speakers = speakers
  }
 
  func numberOfRows() -> Int {
    return speakers.contacts.count
  }
 
  func numberOfSections() -> Int {
    return 1
  }
 
  func getSpeaker(for indexPath: IndexPath) -> Contact {
    return speakers.contacts[indexPath.item]
  }
 
  func selectSpeaker(for indexPath: IndexPath) {
    selectedSpeaker = getSpeaker(for: indexPath)
  }
}
Copy the code

SpeakersListViewController shows a list of attendees, SpeakersViewModel includes: these data from/speakers in the interface of contact objects for the array. SpeakersListViewController will display a speaker in each line. With the ViewModel created, it’s time to configure the cell. Open SpeakerCell. Swift and add the following code to SpeakerCell:

func configure(with contact: Contact) {
  profileImageView.image = UIImage(named: contact.imageName)
  nameLabel.attributedText = NSAttributedString.attributedString(for: contact.firstName, and: contact.lastName)
}
Copy the code

A Contact object is passed in and the image and label of the cell are configured through its properties. This cell will show a photo of the speaker and his name. Next, open the SpeakersListViewController. Swift and add the following code to * viewWillAppear (_) * :

RWService.shared.getSpeakers { [unowned self] speakers in
  if let speakers = speakers {
    self.speakersModel = SpeakersViewModel(speakers: speakers)
    self.tableView.reloadData()
  }
}
Copy the code

GetSpeakers (_:) makes a request to get the speaker list data, creates a * SpeakersViewModel object, and returns speakers. And the TableView is going to update that data. You need to specify a speaker for each row of the TableView to display. Replace tableView(_:cellForRowAt:)*

let cell = tableView.dequeueReusableCell(withIdentifier: "SpeakerCell".for: indexPath) as! SpeakerCell
if letspeaker = speakersModel? .getSpeaker(for: indexPath) {
  cell.configure(with: speaker)
}
return cell
Copy the code

GetSpeaker (for:) returns speaker data based on the indexPath of the current list, and configures the cell via *configure(with:)* of the cell. When clicking on a cell in the list, you need to jump to CardViewController to display the selected speaker information, open cardViewController.swift and add these properties to the class:

var speaker: Contact?

Copy the code

This property will be used later to pass the selected speaker. Replace *// TODO: handle speaker* with:

if let speaker = speaker {
  configure(speaker)
}
Copy the code

This determination is used to determine whether the speaker has been populated, and if so, call configure() to update the speaker’s information on the business card. Back to SpeakersListViewController. Swift transfer choice of speaker. In *tableView(_:didSelectRowAt:)*, above performSegue(withIdentifier: Sender 🙂 add:

speakersModel? .selectSpeaker(for: indexPath)

Copy the code

Mark the corresponding speaker in speakersModel as selected. Next, add the following code after *prepare(for:sender:)* vc.isCurrentUser = false:

vc.speaker = speakersModel? .selectedSpeakerCopy the code

We’re saying that the selectedSpeaker is passed to CardViewController star to display. Make sure your local service is still running, build & run Xcode. You’ll see that the app has integrated the user’s business card with the speaker’s information.

conclusion

You can download the completed project here. In this tutorial, you have learned the basic features of the Protocol buffer, how to define a.proto file and generate Swift files from the compiler. You also learned how to create a simple local server using Flask and send binary data of the Protocol buffer to clients using this service, and how to deserialize data easily. Protocol Buffers also have additional features, such as defining mappings in messages and handling backward compatibility. If you’re interested, check out Google’s documentation.

Finally, Remote Procedure Calls is a project that uses Protocol Buffers and looks very good. Visit GRPC to learn more.