美文网首页
ProtoBuf在iOS中的使用

ProtoBuf在iOS中的使用

作者: Jiangyouhua | 来源:发表于2021-08-16 15:38 被阅读0次

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

    内容概要

    • 首先,创建接收的日志服务器端。
    • 其次,在iOS里使用ProtoBuf。
    • 最后,输出ProtoBuf到文件,再读文件到ProtoBuf。

    日志服务器。

    我们使用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"; 需要在第一行。
    • 成员索引从1开始。
    • 内部枚举索引从0开始。
    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:

    • shard,静态方法,返回一个Logger实例。
    • todayUrl,返回一个以当前日期命名时存储路程径。
    • out,创一个LogInfo实例,并把它转为二进制数据,通过appendLine(),写在日志文件里。
    • in,从当天的日志文件里读取内容并转为LogInfo实例。
    • test, 提供了in、out方法的测试入口。

    再来看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方法。
    • 逐行从日志文件里读取数据并转为LogInfo。
    /// 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解析。

    我是姜友华,下次再见。

    相关文章

      网友评论

          本文标题:ProtoBuf在iOS中的使用

          本文链接:https://www.haomeiwen.com/subject/pffvbltx.html