swift使用swift-protobuf协议通讯,使用指北

什么是Protobuf

Protobuf(Protocol Buffers)协议😉 Protobuf 是一种由 Google 开发的二进制序列化格式和相关的技术,它用于高效地序列化和反序列化结构化数据,通常用于网络通信、数据存储等场景。

为什么要使用Protobuf? 因为快

其实 Protobuf 在许多领域都得到了广泛应用,特别是在分布式系统、RPC(Remote Procedure Call)框架和数据存储中,它提供了一种高效、简洁和可扩展的方式来序列化和交换数据,

Protobuf 的主要优点包括:

高效性:Protobuf 序列化后的二进制数据通常比其他序列化格式(比如超级常用的JSON)更小,并且序列化和反序列化的速度更快,这对于性能敏感的应用非常有益。

简洁性:Protobuf 使用一种定义消息格式的语法,它允许定义字段类型、顺序和规则(消息结构更加清晰和简洁)

版本兼容性:Protobuf 支持向前和向后兼容的版本控制,使得在消息格式发生变化时可以更容易地处理不同版本的通信。

语言无关性:Protobuf 定义的消息格式可以在多种编程语言中使用,这有助于跨语言的通信和数据交换(截至本文发布目前官方支持的有C++/C#/Dart/Go/Java/Kotlin/python/Swift)

自动生成代码:Protobuf 通常与相应的工具一起使用,可以自动生成代码,包括序列化/反序列化代码和相关的类(减少了手动编写代码的工作量,提高效率)

安装编译器

注意:需要swift5.8以上的版本和xcode14.3以后的版本才可以

你也可以通过命令行使用 swift --version 命令来查看安装的Swift版本。这将会输出类似于下面的信息,显示当前安装的Swift编译器的版本:

安装swift-protobuf:这条命令将会给你的电脑上安装 protoc 和 Swift 代码生成器插件。

复制代码
brew install swift-protobuf

根据官方文档可以使用brew安装:

安装完之后,输入protoc将会出现提示:

编译Proto文件

将.proto文件编译为swift代码:--swift_out=. 表示将生成的文件放在当前文件夹中

ini 复制代码
protoc --swift_out=. my.proto

运行之后,就会出现一个GameMsg.pb.swift文件,这就是编译后的swift文件:

生成的代码将为每个 proto 字段公开一个 Swift 属性以及一组序列化和反序列化功能。假如你的proto文件内容是:

ini 复制代码
syntax = "proto3";

message BookInfo {
   int64 id = 1;
   string title = 2;
   string author = 3;
}

那么生成的swift代码:

Swift 复制代码
// DO NOT EDIT.
// swift-format-ignore-file
//
// Generated by the Swift generator plugin for the protocol buffer compiler.
// Source: game.proto
//
// For information on using the generated types, please see the documentation:
//   https://github.com/apple/swift-protobuf/

import Foundation
import SwiftProtobuf

// If the compiler emits an error on this type, it is because this file
// was generated by a version of the `protoc` Swift plug-in that is
// incompatible with the version of SwiftProtobuf to which you are linking.
// Please ensure that you are building against the same version of the API
// that was used to generate this file.
fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck {
  struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {}
  typealias Version = _2
}

struct BookInfo {
  // SwiftProtobuf.Message conformance is added in an extension below. See the
  // `Message` and `Message+*Additions` files in the SwiftProtobuf library for
  // methods supported on all messages.

  var id: Int64 = 0

  var title: String = String()

  var author: String = String()

  var unknownFields = SwiftProtobuf.UnknownStorage()

  init() {}
}

#if swift(>=5.5) && canImport(_Concurrency)
extension BookInfo: @unchecked Sendable {}
#endif  // swift(>=5.5) && canImport(_Concurrency)

// MARK: - Code below here is support for the SwiftProtobuf runtime.

extension BookInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding {
  static let protoMessageName: String = "BookInfo"
  static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
    1: .same(proto: "id"),
    2: .same(proto: "title"),
    3: .same(proto: "author"),
  ]

  mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws {
    while let fieldNumber = try decoder.nextFieldNumber() {
      // The use of inline closures is to circumvent an issue where the compiler
      // allocates stack space for every case branch when no optimizations are
      // enabled. https://github.com/apple/swift-protobuf/issues/1034
      switch fieldNumber {
      case 1: try { try decoder.decodeSingularInt64Field(value: &self.id) }()
      case 2: try { try decoder.decodeSingularStringField(value: &self.title) }()
      case 3: try { try decoder.decodeSingularStringField(value: &self.author) }()
      default: break
      }
    }
  }

  func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws {
    if self.id != 0 {
      try visitor.visitSingularInt64Field(value: self.id, fieldNumber: 1)
    }
    if !self.title.isEmpty {
      try visitor.visitSingularStringField(value: self.title, fieldNumber: 2)
    }
    if !self.author.isEmpty {
      try visitor.visitSingularStringField(value: self.author, fieldNumber: 3)
    }
    try unknownFields.traverse(visitor: &visitor)
  }

  static func ==(lhs: BookInfo, rhs: BookInfo) -> Bool {
    if lhs.id != rhs.id {return false}
    if lhs.title != rhs.title {return false}
    if lhs.author != rhs.author {return false}
    if lhs.unknownFields != rhs.unknownFields {return false}
    return true
  }
}

在项目中使用的话:

Swift 复制代码
// 创建一个BookInfo对象,并给它赋值
var info = BookInfo()
info.id = 1734
info.title = "Really Interesting Book"
info.author = "Jane Smith"

// 如上所述,但生成只读值:
let info2 = BookInfo.with {
    $0.id = 1735
    $0.title = "Even More Interesting"
    $0.author = "Jane Q. Smith"
  }

// 序列化为二进制protobuf格式:可以选择序列化为
// 符合SwiftProtobufContiqueBytes的任何类型。例如
let binaryData: Data = try info.serializedBytes()
let binaryDataAsBytes: [UInt8] = try info.serializedBytes()


// 从`binaryData反序列化接收到的Data对象`
let decodedInfo = try BookInfo(serializedData: binaryData)

// 反序列化从`binaryDataAsBytes接收的[UInt8]对象`
let decodedInfo = try BookInfo(serializedBytes: binaryDataAsBytes)

//序列化为JSON格式的数据对象,或符合
//SwiftProtobufContiqueBytes。例如
let jsonData: Data = try info.jsonUTF8Data()
let jsonBytes: [UInt8] = try info.jsonUTF8Bytes()

// 从`jsonBytes从JSON格式反序列化`
let receivedFromJSON = try BookInfo(jsonUTF8Bytes: jsonBytes)

添加SwiftProtobuf到你的项目中

要想使用Protobuf还需要在你的项目中添加SwiftProtobuf,可以使用SPM添加或者使用CocoaPods添加,我这里习惯使用SPM,在xcode项目中可以直接右键添加包就可以了:

添加之后就可以在左侧项目的依赖包里面显示出来了:

解决遇到的问题

1.如果你在finder中在proto文件夹中生成swift代码,但是当你会到xcode总,发现proto文件夹中不一定会显示proto文件和生成的swift文件:

需要在xcode中手动添加文件到文件夹中:

再回到项目文件夹就可以看到了:

2.但是这个时候,当你打开生成的swift文件,可能发现报错了:

Could not find module 'SwiftProtobuf' for target 'arm64-apple-watchos'; found: arm64_32-apple-watchos, at: /Users/song/Library/Developer/Xcode/DerivedData/mywatch-coaeflxmpfflredurreogfyfvbxz/Index.noindex/Build/Products/Debug-watchos/SwiftProtobuf.swiftmodule

这是因为你没有将 SwiftProtobuf 真正添加到项目中:

需要你手动再将依赖添加到项目中

就不会报错了:

简单的DEMO

我写了一个简单的Demo:

Swift 复制代码
//
//  ContentView.swift
//  mywatch Watch App
//
//  Created by song on 2024/6/19.
//

import SwiftUI

struct ContentView: View {
    // Create a BookInfo object and populate it:
    @State var info = BookInfo()
    // binaryData
    @State var binaryData: Data = .init()
    // binaryInfo
    @State var binaryInfo: BookInfo = .init()

    var body: some View {
        VStack {
            Text(info.author)
            Text("Hello, 1024小神!")
            Button(action: {
                info.id = 1734
                info.title = "Really Interesting Book"
                info.author = "Jane Smith"
            }, label: {
                Text("编辑")
            })
            Button(action: {
                binaryData = try! info.serializedData()
                print("二进制数据:\(binaryData)")
            }, label: {
                Text("序列化")
            })
            Button(action: {
                binaryInfo = try! BookInfo(serializedData: binaryData)
                print("反序列化数据:\(binaryInfo)")
            }, label: {
                Text("反序列化")
            })
        }
        .padding()
    }
}

#Preview {
    ContentView()
}

可以实现协议消息的序列化和反序列化:

相关推荐
崔庆才丨静觅10 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax