這篇文章是講如何把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文件号枕,我們得安裝官方的編譯器。你可以選擇下面任意一種你喜歡的安裝方式:
- 源碼編譯安裝亡脑;https://github.com/protocolbuffers/protobuf/tree/master/objectivec
- 直接下載編譯好的對應(yīng)語言版本的二進(jìn)制文件堕澄;https://github.com/protocolbuffers/protobuf/releases
- 使用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 文件
- 在新工程中創(chuàng)建一個 Protos 目錄抄肖;
真實(shí)的企業(yè)級項(xiàng)目久信,并不會像網(wǎng)上很多教程里一樣只是單純的一兩個 .proto 文件。而是根據(jù)使用模塊的劃分漓摩,會有不同的文件夾裙士,甚至整個存放 .proto 文件的根目錄會作為
git submodule
來存放到遠(yuǎn)端達(dá)到多端共享的目的。Proto源文件的目錄層級管毙,對編譯結(jié)果有很大的影響腿椎,直接關(guān)系到在Xcode中的使用,這是最大的坑點(diǎn)锅风,我們稍后再講酥诽;
在該 Protos 根目錄下再新建兩個子目錄,代表實(shí)際項(xiàng)目中不同的模塊皱埠。為方便記憶一個為a目錄肮帐,一個為b目錄;
在 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.h
和Test.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)中吁断,集成的方式如下:
- 依次進(jìn)入到以下目錄:
Project --> Build Rules --> 點(diǎn)擊+號
,生成一個特定文件類型編譯腳本坞生。
在
Process
中選擇Protobuf source files
仔役;(注意,如果是Xcode10之前的版本并沒有這個選項(xiàng)是己,你需要選擇Source files with names matching
, 然后在后面的輸入框中輸入*.proto
);按照官方教程又兵,添加編譯腳本:
/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):
protoc命令盡量指明絕對路徑,以防腳本編譯時找不到命令的情況沛厨。即
/usr/local/bin/protoc
而不是protoc
宙地。 該點(diǎn)官方文檔倒是沒提到,是我們自己遇到的一個坑逆皮;-
這里需要用到幾個環(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 中查看。
如文檔所言潜索,
--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.h
和 build/gen/Foo.pbobjc.m
文件。
而會把 src/bar/baz.proto
文件編譯成 build/gen/bar/Baz.pbobjc.h
和 build/gen/bar/Baz.pbobjc.m
恩静。
而不是build/gen/Baz.pbobjc.h
和 build/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)需要注意:
當(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)生的文件時也不提示的問題哦~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ù)~