iOS序列化的進階方案——Protocol Buffer

前言

最近項目需要,引入Protocol Buffer來做對象序列化。

正文

Protocol Buffer是Google出的序列化數(shù)據(jù)格式,下面簡稱pb。
我們更常用的序列化數(shù)據(jù)格式應該是json,json和pb本質上都是對象的序列化和反序列化,在項目中json也是前后端通信的主要數(shù)據(jù)格式。
在本地存儲時,我們可以使用YYModel將對象轉成json對應的NSData,也可以使用NSKeyedArchiver結合實現(xiàn)NSCoding協(xié)議把對象轉成NSData,進而將二進制數(shù)據(jù)存儲在沙盒中或者數(shù)據(jù)庫。
那么為什么不使用json,而要用pb?
因為項目中序列化數(shù)據(jù)到沙盒是一個高頻場景,嘗試過數(shù)據(jù)庫、NSCoding+NSKeyedArchiver、YYModel等方法都有各自瓶頸:數(shù)據(jù)內(nèi)容比較大數(shù)據(jù)庫會造成體積膨脹過快不便管理,NSCoding+NSKeyedArchiver在序列化數(shù)據(jù)量較大的情況下性能不佳,YYModel在變動的時候不太友好

相對而言,pb有以下特點:
1、pb是一種可擴展的序列化數(shù)據(jù)數(shù)據(jù)格式,新老版本的數(shù)據(jù)可以相互讀取;
2、pb是使用字節(jié)流方式進行序列化,體積小速度快;(相對而言json是用字符串表示的,光表示字符串的""符號就有很多)
3、pb的代碼是由描述文件proto生成,proto是文本文件便于做版本管理;

pb的使用

使用pb首先要定義proto的數(shù)據(jù)結構,語法非常簡單,可以直接上手寫:

syntax = "proto3";
message LYItemData {
    uint32 itemId = 1;
    string itemContentStr = 2;
}

這里定義一個最簡單的message,第一行是聲明proto的版本,然后添加兩個屬性itemId和itemContentStr;
使用的時候,用[LYItemData parseFromData:data error:nil];可以將NSData轉換成對象,訪問LYItemData類的data屬性,可以拿到其序列化之后的二進制數(shù)據(jù);
代碼很簡單, 序列化和反序列化都只有一行,使用樣例:

    NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"test_data"];
    NSData *data = [[NSData alloc] initWithContentsOfFile:path];
    LYItemData *itemData;
    if (data) {
        itemData = [LYItemData parseFromData:data error:nil]; // 反序列化
    }
    else {
        itemData = [LYItemData new];
        itemData.itemId = (int)time(NULL);
        itemData.itemContentStr = [self timeStampConversionNSString:itemData.itemId];
        [[NSFileManager defaultManager] createFileAtPath:path contents:itemData.data attributes:nil]; // 訪問itemData.data屬性時會做一次序列化
    }

message可以定義容器類型,包括數(shù)組、map等;
定義數(shù)組使用repeated,表示該元素是重復的,數(shù)量從0到若干個不等;
定義字典使用map,map里面帶兩個參數(shù),分別表示key和value的type;

message LYArrayData {
    repeated LYItemData items = 1;
    map<int32, string> idToContentStrMap = 2;
}

也可以在message中聲明另外一個message 的屬性

message LYProtobufLocalData {
    uint64 dataId = 1;
    string dataContentStr = 2;
    uint32 updateTime = 3;
    LYArrayData arrData = 4;
}

了解這些常見的message定義方式,就可以滿足大多數(shù)開發(fā),其他用到再學也不遲。
其他使用方式例如any、oneof、reserved、enum、import、package可以自行探究,我們項目中沒有使用到。
不管哪種定義方式,在定義成員屬性的時候,都需要指定一個數(shù)字,這個數(shù)字是tag,需要保證在類中是唯一的。
tag是屬性的唯一標識符,pb會在存儲和讀取的時候用到這個屬性。

注意事項:
屬性定義之后,tag不能改變;如果有棄用的屬性,最好用reserved聲明其屬性名字和tag;
新老版本都能讀取對應的二進制數(shù)據(jù),對于不認識的屬性會保留默認值。

代碼生成

代碼生成可以和Xcode結合,在每次編譯之后自動生成。
在 Build Phases 里面添加一段腳本(下圖中的Run Proto):先cd到proto所在的目錄,然后運行腳本即可。

cd ${SOURCE_ROOT}/LearnProtoBuf/PB/
./protoc ProtobufData.proto --objc_out=./

記得在對應目錄下添加protoc文件,也可以添加到git倉庫同步其他人使用,這樣別人即使沒安裝proto也可以生成代碼。

如果項目中有多個proto,此處可以使用sh腳本,把路徑名作為參數(shù)傳入,在sh腳本里面分別對每個proto文件做代碼生成。

如果不想使用這種方式,也可以按照傳統(tǒng)方法先安裝protobuf,網(wǎng)上教程比較多,這里不再贅述。

總結

在Restful架構逐漸被RPC架構淘汰的現(xiàn)在,pb取代json作為前后端的通信數(shù)據(jù)格式也是時代的潮流。
json最大的優(yōu)勢或許是后端已有的很多服務都是用json通信,一時間無法完全替換。
pb簡單易用,對持續(xù)變更更加友好。
一次定義,多端使用;
版本更迭,格式兼容。

附錄

官方參考文檔--OC代碼生成
PB-Github
二進制encode原理

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容