ProtoBuf在iOS中的使用

2021-08-16  本文已影响0人  Jiangyouhua

Hi, 大家好,我是姜友华。
在没有了解到ProtoBuf之前,我在后台开发中输出日志一般有两种:一种是基于现有格式,如JSON, XML等;另一种是基于约定输出字符串行。第一种大家都知道,第二种是类似于数库备份时导出CSV文件一样。

内容概要


日志服务器。

我们使用go来建立一个最简的服务器,用来接收上传的文件。你当然也可以用其它的方式搭建一个。
下面是Go的代码,使用下面代码前,你需要有Go官网的开发环境。

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", uploadFile)
    err := http.ListenAndServe(":80", nil)
    if err != nil {
        log.Fatal(err)
    }
}

func uploadFile(w http.ResponseWriter, r *http.Request) {
    file, handler, err := r.FormFile("file")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    defer file.Close()
    b, err := ioutil.ReadAll(file)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    err = ioutil.WriteFile(handler.Filename, b, 0644)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    fmt.Fprintf(w, "Upload successful")
}

运行它会监听本地的80端口。

jingo1997@163.com logServer % go run main.go

使用Postman测试一下,可行。


在iOS添加ProtoBuf

在本例中,我们将使用Proto3。Swift里使用ProtoBuf,需要引用第三方库。我选择了这个库Swift ProtoBuf

iOS里是如何使用ProtoBuf的呢?

使用ProtoBuf分三步:首先,按Proto3的方式定义一个信息体,并保存;然后,使用Protoc工具将该信息体转为Swift结构体;最后,Swift使用该结构体进行日志输出。

  1. 安装Protoc工具。
$ brew install swift-protobuf.

将库引入到工程。
我们是通过Xocde自带的包管理引入的。


Screen Shot 2021-08-16 at 18.19.37.png
  1. 添加日志信息体: LogFile.proto。
syntax = "proto3";

message LogInfo {
    int32 id = 1;
    string content = 2;
    enum Event {
        UNIVERSAL = 0;
        WEB = 1;
        IMAGES = 2;
        LOCAL = 3;
        NEWS = 4;
        PRODUCTS = 5;
        VIDEO = 6;
    }
    enum State {
        SUCCESS = 0;
        INFO = 1;
        ALART = 2;
        ERROR = 3;
    }
    Event event = 3;
    State state = 4;
}
  1. 转换日志信息体。
    进入到logFile.proto的根目录,运行下面命令,输出一个LogInfo.pb.swift的文件。
$ protoc --swift_out=. logFile.proto
  1. 使用ProtoBuf。
    我们创建一个Logger结构体,用来调用用ProtoBuf。
    先贴代码:
/// Logger
struct Logger {
    static var shared = Logger()
    
    private var todayUrl: URL? {
        let dateFormatter: DateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyyMMdd"
        let path = "\(dateFormatter.string(from: Date())).log"
        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent(path)
    }
    
    func out(id: Int, content: String, event: LogInfo.Event, state: LogInfo.State){
        var log = LogInfo()
        log.id = Int32(id)
        log.content = content
        log.event = event
        log.state = state

        do {
            let binaryData = try log.serializedData()
            try binaryData.appendLine(to: todayUrl)
        } catch {
            print("Logger.out: \(error)")
            return
        }
    }
    
    func `in`() -> [LogInfo] {
        var logs = [LogInfo]()
        guard let todayUrl = todayUrl else {
            print("Logger.read: Log url is error.")
            return logs
        }
        if freopen(todayUrl.path, "r", stdin) == nil {
            return logs
        }
        while let line = readLine() {
            guard let log = try? LogInfo(serializedData: line.data(using: .utf8)!) else {
                print("Line: ", line)
                break
            }
            logs.append(log)
        }
        print("Log: ", logs.first)
        return logs
    }
    
    func test(isIn: Bool = false){
        // 从日志里读取。
        if isIn {
            self.in()
            return
        }
        // 写到日志。
        let contents = ["星期一Monday简写为Mon","星期二Tuesday简写为Tue","星期三Wednesday简写为Wed","星期四Thursday简写为Thu","星期五Friday简写为Fri","星期六Saturday简写为Sat","星期日Sunday简写为Sun"]
        for (i, item) in contents.enumerated() {
            self.out(id: i, content: item, event: .web, state: .info)
        }
        
    }
}
// 写。
extension Data {
    func appendLine(to url: URL?) throws {
        guard let url = url else {
            return
        }
        print("Path:", url.path)
        if let fileHandle = try? FileHandle(forWritingTo: url) {
            defer {
                fileHandle.closeFile()
            }
            fileHandle.seekToEndOfFile()
            fileHandle.write("\n".data(using: .utf8)!)
            fileHandle.write(self)
        } else {
            try write(to: url)
        }
    }
}

在这里我们做了两件事:

  1. 通过ProtoBuf,将数据写到日志文件上。
  2. 从日志文件读数据,转为ProtoBuf对象。
    为了做这两件事,我们定义了一个Logger的结构体,同时拓展了Data类为其添加了一个appendLine的方法。

先来看结构体Logger:

再来看Data类的appendLine方法:
*逐行写入每个LogInfo实例的二进制数据。
注意,各个LogInfo实例二进制数据之间的分割是通过换行("\n")实现的。

本地测试一下:

  1. 输出 LogInfo。
    我们在AppDelegate中调用Logger的test方法。
/// AppDelegate
......
    Logger.shared.test()
......

打开path/to/20210817.log文件:
是不是跟CSV文件相似,但还是有一点不同,我们的event, state的数据呢?

�星期一Monday简写为Mon�� �
�星期二Tuesday简写为Tue�� �
�星期三Wednesday简写为Wed�� �
�星期四Thursday简写为Thu�� �
�星期五Friday简写为Fri�� �
�星期六Saturday简写为Sat�� �
�星期日Sunday简写为Sun�� �� �
  1. 转入 LogInfo。
    我们在AppDelegate中调用Logger的test方法。
/// AppDelegate
......
    Logger.shared.test(isIn: true)
......
Log:  Optional(study.LogInfo:
content: "星期一Monday简写为Mon"
event: WEB
state: INFO
)

输出结果与测试输入的数据相同,应用该可以了。
等一下,其实缺少了id的数据。

Log:  Optional(study.LogInfo:
id: 6
content: "星期日Sunday简写为Sun"
event: WEB
state: INFO
)

这个有id值,这就对了。
好,这一节就到这里,下一节添加服务器端ProtoBuf解析。

我是姜友华,下次再见。

上一篇下一篇

猜你喜欢

热点阅读