Protocol Buffers(Objective-C)踩坑指南

這篇文章是講如何把protobuf文件的編譯工作集成到Xcode中狈谊,達(dá)到在Xcode中就像添加一般的OC文件一樣不進(jìn)行任何多余的操作直接編譯運(yùn)行.proto文件的目的赎瞎。

牛逼敞临,這么智能嗎票髓?是的怒详,就是這么智能!

筆者的公司現(xiàn)在所有端都在統(tǒng)一使用一套protobuf數(shù)據(jù)結(jié)構(gòu)拦盹,免除了多端重復(fù)定義同一套數(shù)據(jù)結(jié)構(gòu)的重復(fù)工作沼侣,效率很高却特,非常值得推薦扳碍。并且Xcode 10進(jìn)行了一些小優(yōu)化來增加了對Protobuf的支持,相信不久以后后雷,Xcode對Protobuf的支持將更加智能磕瓷!

至于什么是 Protobuf 和 Protobuf 語法教程,不是這篇文章的主題焰坪,請自行Google禀酱。

環(huán)境:Xcode 10+
語言:Objective-C

話不多說臊恋,正題開始:

首先放吩,真正的企業(yè)級項(xiàng)目唧喉,并不只是網(wǎng)上很多教程里面演示的一兩個 .proto 文件驯鳖,而是一批 .proto 文件目錄的集合,并且是多端共享的呼巴。你會發(fā)現(xiàn)按照那些教程里面的講的去做寫個demo或許可以碧磅,但是真正要達(dá)到企業(yè)級別的使用的時候丰榴,還遠(yuǎn)遠(yuǎn)不夠拨匆,你會遇到各種各樣的坑。別問我是怎么知道的,我都是靠自己一個個踩出來的岩调。

安裝編譯工具

首先赡盘,要能編譯Protobuf文件号枕,我們得安裝官方的編譯器。你可以選擇下面任意一種你喜歡的安裝方式:

  1. 源碼編譯安裝亡脑;https://github.com/protocolbuffers/protobuf/tree/master/objectivec
  2. 直接下載編譯好的對應(yīng)語言版本的二進(jìn)制文件堕澄;https://github.com/protocolbuffers/protobuf/releases
  3. 使用brew;brew install protobuf;

安裝好后霉咨,在terminal中輸入which protoc檢測是否安裝成功,如安裝成功會返回文件路徑: /usr/local/bin/protoc

如有問題蛙紫,請自行g(shù)oogle,不在本教程范圍內(nèi)途戒。

在 Xcode 項(xiàng)目中集成 Protobuf 庫

沒什么好說的坑傅,新建一個Xcode工程。使用Cocoapods引入Protobuf的庫:

Pod search Protobuf

選擇最穩(wěn)定的版本即可喷斋。

坑點(diǎn)一:到這里唁毒,需要注意的是編譯器和Pod引入的Protobuf Framework的版本需要對應(yīng)。比如你的編譯工具是3.9.0版本星爪,那么Protobuf版本最好也是3.9.0浆西。如果后期升級Pod的Protobuf庫,那么編譯工具也需要跟隨升級顽腾。版本不一致近零,可能會導(dǎo)致項(xiàng)目在運(yùn)行時出現(xiàn)編譯出錯哦!

創(chuàng)建 .proto 文件

  1. 在新工程中創(chuàng)建一個 Protos 目錄抄肖;

真實(shí)的企業(yè)級項(xiàng)目久信,并不會像網(wǎng)上很多教程里一樣只是單純的一兩個 .proto 文件。而是根據(jù)使用模塊的劃分漓摩,會有不同的文件夾裙士,甚至整個存放 .proto 文件的根目錄會作為 git submodule 來存放到遠(yuǎn)端達(dá)到多端共享的目的。Proto源文件的目錄層級管毙,對編譯結(jié)果有很大的影響腿椎,直接關(guān)系到在Xcode中的使用,這是最大的坑點(diǎn)锅风,我們稍后再講酥诽;

  1. 在該 Protos 根目錄下再新建兩個子目錄,代表實(shí)際項(xiàng)目中不同的模塊皱埠。為方便記憶一個為a目錄肮帐,一個為b目錄;

  2. 在 a 目錄下創(chuàng)建 A.proto 源文件。在 b 目錄下創(chuàng)建 B.proto 文件训枢;

這里有兩種創(chuàng)建.proto文件的方式:

  • 通過命令行創(chuàng)建托修,創(chuàng)建好之后需要拖到Xcode項(xiàng)目下;
  • 直接在Xcode中通過右鍵A目錄恒界,選擇 New File 睦刃,然后依次選擇 iOS --> Other --> Empty , 文件名加上 .proto 后綴即可。

坑點(diǎn)二:.proto的文件名格式一定是大駝峰寫法十酣。即一定要以大寫字母開頭涩拙。因?yàn)榧词刮募切懀罱K編譯出來的是結(jié)果也是大駝峰格式命名的文件耸采。比如 test.proto 編譯出來的是 Test.pbobjc.hTest.pbobjc.m文件 兴泥;

至于文件內(nèi)容,如果你熟悉protobuf語法虾宇,那隨便寫幾行即可搓彻,如果不熟悉,那么可以copy我的測試內(nèi)容:

A.proto 文件內(nèi)容:

syntax = "proto3";

import "b/b.proto"; // 在A.proto文件中引入b/b.proto文件嘱朽,一定要指明路徑哦~

option objc_class_prefix = "PXL";

package a; 

message TestA {
    string name = 1;
    b.TestB test = 2;
}

B.proto 文件內(nèi)容:

syntax = "proto3";

option objc_class_prefix = "PXL";

package b;

message TestB {
    string name = 1;
}

坑點(diǎn)三:注意旭贬,無論以上面哪種方式創(chuàng)建。在Xcode10以前的版本搪泳,創(chuàng)建好文件后稀轨,需要到Project --> Build Phases --> Compile Sources 中,把剛才新建的a.proto和b.proto文件添加進(jìn)去岸军。什么意思呢靶端?就是說要把這兩個文件添加到可編譯文件里面。只有可編譯文件凛膏,我們才能對其進(jìn)行后續(xù)的自定義編譯;Xcode10不用脏榆,Xcode10已經(jīng)針對Protobuf進(jìn)行了一些專門的優(yōu)化猖毫。

為工程添加自定義編譯腳本

Xcode 自己并不認(rèn)識 .proto文件,所以并不會自動編譯它們须喂,我們需要把 .proto編譯器 自己集成到項(xiàng)目當(dāng)中吁断,集成的方式如下:

  1. 依次進(jìn)入到以下目錄:

Project --> Build Rules --> 點(diǎn)擊+號,生成一個特定文件類型編譯腳本坞生。

  1. Process中選擇Protobuf source files仔役;(注意,如果是Xcode10之前的版本并沒有這個選項(xiàng)是己,你需要選擇Source files with names matching, 然后在后面的輸入框中輸入*.proto);

  2. 按照官方教程又兵,添加編譯腳本:

/usr/local/bin/protoc --proto_path=${SRCROOT}/<你的工程目錄名稱>/protos/ --objc_out=${DERIVED_FILE_DIR} $INPUT_FILE_PATH 

比如:

/usr/local/bin/protoc --proto_path=${SRCROOT}/ProtoTests/protos/ --objc_out=${DERIVED_FILE_DIR} $INPUT_FILE_PATH

到此處,我們有幾個注意事項(xiàng):

  1. protoc命令盡量指明絕對路徑,以防腳本編譯時找不到命令的情況沛厨。即/usr/local/bin/protoc 而不是protoc宙地。 該點(diǎn)官方文檔倒是沒提到,是我們自己遇到的一個坑逆皮;

  2. 這里需要用到幾個環(huán)境變量:

    ${SRCROOT} 是Xcode自帶環(huán)境變量宅粥,代表工程根目錄;

    ${INPUT_FILE_PATH} 代表腳本執(zhí)行文件的絕對輸入路徑,包含文件名本身电谣,并且?guī)募袷剑?/p>

    ${INPUT_FILE_BASE} 代表腳本執(zhí)行文件的文件名秽梅,不包含后綴格式;

    ${INPUT_FILE_NAME} 代表腳本執(zhí)行文件的文件名剿牺,包含后綴格式企垦;

    ${DERIVED_FILE_DIR} 代表Xcode的文件輸出目錄;

    其他Xcode自帶環(huán)境變量https://gist.github.com/gdavis/6670468牢贸。當(dāng)然竹观,你也可以在項(xiàng)目 build log 中查看。

  3. 如文檔所言潜索,--proto_path對應(yīng)的路徑是proto源文件的絕對根目錄臭增。--objc_out是編譯產(chǎn)生文件的存放目錄。

為什么--proto_path 需要是絕對根目錄呢竹习?

我們試試把 --proto_path 換成相對路徑誊抛,看會發(fā)生什么,也就是把腳本換成

cd ${SRCROOT}/ProtoTests/protos/
/usr/local/bin/protoc --proto_path=./ --objc_out=${DERIVED_FILE_DIR} $INPUT_FILE_PATH

編譯運(yùn)行整陌,咦~報(bào)錯了拗窃。查看日志,我們可以看到這么一條log信息:

File does not reside within any path specified using --proto_path (or -I).  You must specify a --proto_path which encompasses this file.  Note that the proto_path must be an exact prefix of the .proto file names -- protoc is too dumb to figure out when two paths (e.g. absolute and relative) are equivalent (it's harder than you think).

翻譯過來就是在--proto_path這個參數(shù)中你必須指定.proto源文件的精確路徑泌辫,protoc太笨了随夸,它無法搞清楚這個相對路徑是不是我們要的絕對路徑。google的工程師說這太他么難了震放。所以這里很明確了宾毒,--proto_path 的參數(shù)值,只能是proto文件根目錄的絕對路徑殿遂。

那我們?yōu)槭裁匆?code>$INPUT_FILE_PATH?

我們上面說了诈铛,${INPUT_FILE_PATH} 是代表編譯輸入源文件的絕對路徑。

文檔里面給的demo是:
protoc --proto_path=src --objc_out=build/gen src/foo.proto src/bar/baz.proto

什么意思呢墨礁?

它說幢竹,最終編譯器會把src/foo.proto文件編譯成:build/gen/Foo.pbobjc.hbuild/gen/Foo.pbobjc.m 文件。
而會把 src/bar/baz.proto 文件編譯成 build/gen/bar/Baz.pbobjc.hbuild/gen/bar/Baz.pbobjc.m恩静。
而不是build/gen/Baz.pbobjc.hbuild/gen/Baz.pbobjc.m

也就是說protobuf編譯器最終生成的文件會自動按照文件源目錄結(jié)構(gòu)存放焕毫。

特別強(qiáng)調(diào) 并不會 自動創(chuàng)建 build/gen 目錄,這個目錄需要你提前建好。

并且咬荷,查看最終編譯生成的.m文件冠句,你會發(fā)現(xiàn)一些有趣的事情;比如我在A.proto中引入了B.proto文件幸乒,你會看到Protobuf最終編譯出來的A.pbobjc.m文件導(dǎo)入文件的格式是包含文件路徑的懦底,例如:

import "a/A.pbobjc.h"
import "b/B.pbobjc.h"

設(shè)置編譯文件輸出路徑

我們注意到,上面設(shè)置的proto文件的編譯輸出路徑是 $DERIVED_FILE_DIR罕扎, 這是為何呢聚唐?

答案是為了方便Xcode的集成。

對于自定義的編譯腳本腔召,都需要設(shè)置一個文件的輸出路徑.

我們點(diǎn)腳本框下面的Output Files下面的+號, 指定文件輸出路徑杆查。
因?yàn)镺C文件分為.h和.m文件,所以我們指定2個臀蛛。

點(diǎn)了之后亲桦,你會發(fā)現(xiàn),xcode默認(rèn)給出的是 $(DERIVED_FILE_DIR)/newOutputFile,
我們將其改為$(DERIVED_FILE_DIR)/${INPUT_FILE_BASE}.pbobjc.h$(DERIVED_FILE_DIR)/${INPUT_FILE_BASE}.pbobjc.m浊仆,并且在.m文件的Compiler Flags中指定為-fno-objc-arc代表該.m文件采用mrc編譯客峭。

編譯運(yùn)行,大功告成抡柿,是不可能的L蚶拧!V蘖印备蚓!

你會發(fā)現(xiàn)又報(bào)錯了:

clang: error: no such file or directory: '~/Library/Developer/Xcode/DerivedData/ProtoTests-dpojqcqwplnmyzbgdvjiqjfefgky/Build/Intermediates.noindex/ProtoTests.build/Debug-iphonesimulator/ProtoTests.build/DerivedSources/A.pbobjc.m'

什么意思呢? 其實(shí)就是在 DerivedSources 下找不到 A.pbobjc.m 文件囱稽。因?yàn)槲覀冎付ㄟ@個編譯的輸出路徑在這個目錄下郊尝,所以Xcode在進(jìn)行OC文件的編譯時會去這個目錄下找,但是它找不到战惊。為什么找不到呢虚循?我們?nèi)ミ@個目錄下看,這個目錄下確實(shí)沒有 A.pbobjc.m 這個文件样傍,但是確發(fā)現(xiàn)有 a/A.pbobjc.m。原因我們已經(jīng)說了铺遂,protoc最終的編譯文件會自動加上目錄前綴衫哥。

有人可能會說,能不能把輸出文件改成 $(DERIVED_FILE_DIR)/*/${INPUT_FILE_BASE}.pbobjc.h 呢襟锐?那我們就來試下撤逢。

編譯運(yùn)行

what the hell?

clang: error: no such file or directory: '~/Library/Developer/Xcode/DerivedData/ProtoTests-dpojqcqwplnmyzbgdvjiqjfefgky/Build/Intermediates.noindex/ProtoTests.build/Debug-iphonesimulator/ProtoTests.build/DerivedSources/*/A.pbobjc.m'

原來,Xcode的Output Files特別蠢,它不支持類似這種通配符寫法: $(DERIVED_FILE_DIR)/*/${INPUT_FILE_BASE}.pbobjc.h蚊荣。
也不支持傳入任何的自定義變量初狰。

只能是明確的文件路徑和Xcode自帶的環(huán)境變量,但是實(shí)際項(xiàng)目中互例,可能不只一層路徑奢入,有可能是文件夾下嵌套文件夾。

靠媳叨,那這怎么辦呢腥光?

實(shí)在沒辦法了,就在打算放棄的時候糊秆,咨詢了我們的腳本大神武福,我們嘗試了以下在腳本末尾再加了兩行:

# cd ${DERIVED_FILE_DIR}
# find . -mindepth 2 -name ${INPUT_FILE_BASE}.pbobjc.m -o -name ${INPUT_FILE_BASE}.pbobjc.h | xargs -I{} cp "{}" .

是不是很機(jī)智?

什么意思呢痘番?就是說我們cd到該目錄捉片,然后找到該文件對應(yīng)生成的oc文件,將其copy一份兒到根目錄汞舱。懷著求神拜佛的意志伍纫,運(yùn)行了以下,Perfect兵拢,終于不再報(bào)錯了翻斟,到目錄中查看,也正是我們想要的说铃,所有文件都被copy出來了访惜。

下一步,就是正常的在項(xiàng)目中import和使用了腻扇。

Use it

你以為到此就沒有坑了嗎债热?到此還有坑。有2點(diǎn)需要注意:

  1. 當(dāng)我們在import這些生成的OC文件的時候幼苛,如果你用的是Xcode的 新編譯系統(tǒng)窒篱,你在import的時候應(yīng)該使用 #import <B.pbobjc.h> ,你會發(fā)現(xiàn) #import "B.pbobjc.h" 也可以,但是Xcode不會給你提示舶沿。怎么辦呢墙杯?將Xcode設(shè)置為老編譯系統(tǒng)就可以了。設(shè)置方式:File --> Workspace Settings,將 New Build System 改為 Legacy Build System 括荡;悄悄地告訴你高镐,這個設(shè)置可以解決Xcode在import其他非Protobuf編譯產(chǎn)生的文件時也不提示的問題哦~

  2. import的方式是選擇 #import "B.pbobjc.h" 還是 #import "b/B.pbobjc.h" 』澹看你喜歡嫉髓,并且要統(tǒng)一观腊,不過建議采用帶目錄的這種方式,一來是Protobuf自己產(chǎn)生的文件是這樣做的算行,二來以后xcode的輸出文件目錄變得更智能時梧油,一定是會支持這種方式的。

好了州邢,就講到這里吧儡陨,如果覺得文章看得不是很明白,需要一個demo偷霉∑或者大神有更好的建議,請?jiān)谠u論區(qū)留言~

如果文章對你有幫助类少,請不要吝嗇你的點(diǎn)贊哦叙身,你的支持是我分享的動力~

如果大家喜歡,有時間再講講怎么改改AFNetworking硫狞,能直接請求后端給的 Protobuf 格式的數(shù)據(jù)~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末信轿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子残吩,更是在濱河造成了極大的恐慌财忽,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泣侮,死亡現(xiàn)場離奇詭異即彪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)活尊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門隶校,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蛹锰,你說我怎么就攤上這事深胳。” “怎么了铜犬?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵舞终,是天一觀的道長。 經(jīng)常有香客問我癣猾,道長敛劝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任纷宇,我火速辦了婚禮夸盟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呐粘。我一直安慰自己满俗,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布作岖。 她就那樣靜靜地躺著唆垃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪痘儡。 梳的紋絲不亂的頭發(fā)上辕万,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機(jī)與錄音沉删,去河邊找鬼渐尿。 笑死,一個胖子當(dāng)著我的面吹牛矾瑰,可吹牛的內(nèi)容都是我干的砖茸。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼殴穴,長吁一口氣:“原來是場噩夢啊……” “哼凉夯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起采幌,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤劲够,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后休傍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體征绎,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年磨取,在試婚紗的時候發(fā)現(xiàn)自己被綠了人柿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡寝衫,死狀恐怖顷扩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慰毅,我是刑警寧澤隘截,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站汹胃,受9級特大地震影響婶芭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜着饥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一犀农、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宰掉,春花似錦呵哨、人聲如沸赁濒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拒炎。三九已至,卻和暖如春挨务,著一層夾襖步出監(jiān)牢的瞬間击你,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工谎柄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丁侄,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓朝巫,卻偏偏與公主長得像鸿摇,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子捍歪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容