djinni簡析

djinni是Dropbox開源的一個庫囤热,它會根據(jù)IDL定義生成相應(yīng)的接口和很多膠水代碼猎提,可以做到Mac、PC旁蔼、iOS锨苏、Android四個端共享接口和C++基礎(chǔ)代碼疙教。

本文僅從iOS的角度分析一下怎么djinni的基礎(chǔ)使用方法。djinni的調(diào)用路徑是C++->ObjectiveC++->Objective-C伞租,所以從每種語言的層面做一些分析贞谓。

整個的關(guān)系圖如下所示。調(diào)用路徑是WLALoginSDK->cppRef(WLALoginSDKImpl)->djinni_generated::LoginProvider->ObjcRef(WLALoginProviderImpl)葵诈。如果未來有C++實現(xiàn)的OAuth 2.0客戶端裸弦,調(diào)用邏輯可以做很多簡化,不需要一個provider去調(diào)用AppAuth庫作喘。

Paste_Image.png
Paste_Image.png

腳本

從腳本可以看出C++代碼的namespace是wla_gen理疙,而Objective-C++代碼的namespace是djinni_generated。Objective-C相關(guān)的類和協(xié)議都會加上objc-type-prefix前綴泞坦,也就是腳本里面配置的WLA沪斟。記住這些規(guī)律,有利于分析每個類的角色暇矫。

../djinni_exe/run \
   --java-out ./app/src/main/java/com/aliyun/wla/login \
   --java-package com.aliyun.wla.login \
   --java-cpp-exception java.lang.RuntimeException \
   --ident-java-field mFooBar \
   --cpp-out ./djinni_gen_cpp \
   --cpp-namespace wla_gen \
   --cpp-libexport WLA_LOGIN_EXPORT \
   --jni-out ./djinni_gen_jni \
   --ident-jni-class NativeFooBar \
   --ident-jni-file NativeFooBar \
   --objc-out ./djinni_gen_oc \
   --objc-type-prefix WLA \
   --objcpp-out ./djinni_gen_oc \
   --objcpp-namespace djinni_generated \
   --idl ./all.djinni

IDL

一份IDL定義如下所示。其中LoginSDK的具體邏輯要以C++實現(xiàn)择吊,向Objective-C暴露一個WLALoginSDK類李根,但是它主要調(diào)用LoginProvider干活。LoginProvider通過C++定義了接口几睛,但是通過WLALoginProviderImpl這個Objective-C類來實現(xiàn)具體的邏輯房轿,比如OAuth的認證流程采用OpenID下面的AppAuth-iOS和AppAuth-Android來完成,這些庫并不是C++寫的所森。

//+c 表示用C++實現(xiàn)邏輯囱持,暴露給Objective-C使用。
//+c 會在Objective-C生成一個 @interface WLALoginSDK焕济。
LoginSDK = interface +c {
    static sharedInstance(): LoginSDK;
    
    setLoginProvider(provider: LoginProvider);
    
    setClientID(client_id: string);
}

# wrapper of each platform's OAuth SDK
// +o +j 表示要用各端的代碼實現(xiàn)邏輯
// 會在Objective-C生成一個 @protocol WLALoginProvider纷妆,需要有一個Objective-C實現(xiàn)類。
LoginProvider = interface +o +j {
    setClientID(client_id: string);
}

C++

不管+c還是+o +j晴弃,都會生成一個C++純虛類掩幢。

//LoginSDK.hpp
namespace wla_gen {

class WLA_LOGIN_EXPORT LoginSDK {
public:
    virtual ~LoginSDK() {}

    static std::shared_ptr<LoginSDK> sharedInstance();

    virtual void setLoginProvider(const std::shared_ptr<LoginProvider> & provider) = 0;

    virtual void setClientID(const std::string & client_id) = 0;
};

}  // namespace wla_gen

//LoginProvider.hpp
namespace wla_gen {

class LoginProvider {
public:
    virtual ~LoginProvider() {}

    virtual void setClientID(const std::string & client_id) = 0;
};

}  // namespace wla_gen

Objective-C++

不管+c還是+o +j,都會生成一個Objective-C++的類上鞠,文件名是xxx+private.hxxx+private.mm际邻。Objective-C++類有兩個主要的作用就是toCpp和fromCpp,讓C++對象和Objective-C對象可以互相找到彼此芍阎。針對+c+o +j生成的代碼有所不同世曾。下面分兩種情況分析一下。

+c

主要是完善WLALoginSDK類谴咸,它持有一個::wla_gen::LoginSDK純虛類的實現(xiàn)轮听,干活都是這個cppRef骗露。

namespace djinni_generated {

class LoginSDK
{
public:
    using CppType = std::shared_ptr<::wla_gen::LoginSDK>;
    using CppOptType = std::shared_ptr<::wla_gen::LoginSDK>;
    using ObjcType = WLALoginSDK*;

    using Boxed = LoginSDK;

    static CppType toCpp(ObjcType objc);
    static ObjcType fromCppOpt(const CppOptType& cpp);
    static ObjcType fromCpp(const CppType& cpp) { return fromCppOpt(cpp); }

private:
    class ObjcProxy;
};

}  // namespace djinni_generated
@interface WLALoginSDK ()

- (id)initWithCpp:(const std::shared_ptr<::wla_gen::LoginSDK>&)cppRef;

@end

@implementation WLALoginSDK {
    ::djinni::CppProxyCache::Handle<std::shared_ptr<::wla_gen::LoginSDK>> _cppRefHandle;
}

- (id)initWithCpp:(const std::shared_ptr<::wla_gen::LoginSDK>&)cppRef
{
    if (self = [super init]) {
        _cppRefHandle.assign(cppRef);
    }
    return self;
}

+ (nullable WLALoginSDK *)sharedInstance {
    try {
        auto objcpp_result_ = ::wla_gen::LoginSDK::sharedInstance();
        return ::djinni_generated::LoginSDK::fromCpp(objcpp_result_);
    } DJINNI_TRANSLATE_EXCEPTIONS()
}

- (void)setLoginProvider:(nullable id<WLALoginProvider>)provider {
    try {
        _cppRefHandle.get()->setLoginProvider(::djinni_generated::LoginProvider::toCpp(provider));
    } DJINNI_TRANSLATE_EXCEPTIONS()
}

+o +j

而LoginProvider則實現(xiàn)了::wla_gen::LoginProvider::djinni::ObjcProxyCache::Handle<ObjcType>,一看就知道要甩鍋給一個Objective-C對象蕊程。

namespace djinni_generated {

class LoginProvider
{
public:
    using CppType = std::shared_ptr<::wla_gen::LoginProvider>;
    using CppOptType = std::shared_ptr<::wla_gen::LoginProvider>;
    using ObjcType = id<WLALoginProvider>;

    using Boxed = LoginProvider;

    static CppType toCpp(ObjcType objc);
    static ObjcType fromCppOpt(const CppOptType& cpp);
    static ObjcType fromCpp(const CppType& cpp) { return fromCppOpt(cpp); }

private:
    class ObjcProxy;
};

}  // namespace djinni_generated
//隱藏在WLALoginProvider+Private.mm文件中
class LoginProvider::ObjcProxy final
: public ::wla_gen::LoginProvider
, public ::djinni::ObjcProxyCache::Handle<ObjcType>
{
public:
    using Handle::Handle;
    void setClientID(const std::string & c_client_id) override
    {
        @autoreleasepool {
            [Handle::get() setClientID:(::djinni::String::fromCpp(c_client_id))];
        }
    }

LoginSDKImpl的邏輯主要是C++寫的椒袍,主要是調(diào)LoginProvider做各種事情。這個地方可以寫一些平臺同樣的邏輯藻茂,比如token的存取等驹暑。

void LoginSDKImp::setLoginProvider(const std::shared_ptr<wla_gen::LoginProvider> & provider)
{
    _provider = provider;
}

void LoginSDKImp::setClientID(const std::string & client_id)
{
    if (_provider)
        _provider->setClientID(client_id);
}

Objective-C

LoginProvider要Objective-C來實現(xiàn)具體的邏輯,所以要有一個實現(xiàn)WLALoginProvider協(xié)議的類辨赐。

@interface WLALoginProviderImpl : NSObject <WLALoginProvider>

@end

@interface WLALoginProviderImpl ()

@property(nonatomic, strong, nullable) id<OIDAuthorizationFlowSession> currentAuthorizationFlow;

@end

@implementation WLALoginProviderImpl

- (void)login:(nullable id<WLALoginCallback>)callback {
    //使用AppAuth-iOS發(fā)起OAuth 2.0認證
}

djinni不足之處

djinni無法表達每個端專有的特性优俘,具體到iOS端,有如下不足掀序。

  1. 無法表達nonull帆焕、nullable。對于普通屬性統(tǒng)統(tǒng)使用nonull不恭,對于callback則使用nullable叶雹。
  2. 無法表達協(xié)議的@optional。由于IDL往往是各端接口的超集换吧,iOS只會實現(xiàn)其中某些接口折晦,這樣會導致很多警告。
  3. 跟所有IDL一樣沾瓦,無法使用各端特有的數(shù)據(jù)類型满着,無法使用泛型。
  4. 以上缺點都能忍贯莺,callback的問題實在是很難忍受风喇,Objective-C和C++的Callback寫起來都很麻煩。

Objective-C callback

所有+o +j的callback都會變成協(xié)議缕探,需要一個Objective-C包裝類,使用起來非常之麻煩魂莫。

@interface WLALoginCallbackImpl : NSObject <WLALoginCallback>

- (instancetype)initWithBlock:(void(^)(WLAUser *, WLAError *))block;

@end

@interface WLALoginCallbackImpl ()

@property (nonatomic, copy) void(^block)(WLAUser *user, WLAError *error);

@end

@implementation WLALoginCallbackImpl

- (instancetype)initWithBlock:(void (^)(WLAUser *, WLAError *))block
{
    self = [super init];
    if (self) {
        self.block = block;
    }

    return self;
}

- (void)call:(nonnull WLAError *)error
        user:(nonnull WLAUser *)user {
    if (self.block) {
        self.block(user, error);
    }
}

@end

//使用起來一點都沒有block那種行云流水的感覺
[[WLALoginSDK sharedInstance] login: [[WLALoginCallbackImpl alloc]
                                      initWithBlock:^(WLAUser *user, WLAError *error) {
}]];

C++ callback

C++層的block需要使用makeCallback構(gòu)造出來,不算方便爹耗,但是比起Objective-C還是要好一些的豁鲤。

void LoginSDKImp::login(const std::shared_ptr<wla_gen::LoginCallback> & callback)
{
    if (_provider) {
        std::weak_ptr<LoginSDKImp> weak_this = shared_from_this();
        auto provider_callback = wla::makeCallback<wla_gen::ProviderLoginCallback>([weak_this, callback](const wla_gen::Error &err, const string &response) {
            auto strong_this = weak_this.lock();
            if (!strong_this)
                return;

            strong_this->handleLoginResponse(err, response, callback);
        });

        _provider->login(provider_callback);
    }
}

make_callBack包裝了一個lambda閉包。反正現(xiàn)在C++ 11的代碼鬼都看不懂鲸沮,貼一個lambda閉包的例子吧琳骡。

#include <vector>
#include <iostream>
#include <algorithm>
#include <functional>

int main()
{
    std::vector<int> c = {1, 2, 3, 4, 5, 6, 7};
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; }), c.end());

    std::cout << "c: ";
    std::for_each(c.begin(), c.end(), [](int n){ std::cout << n << ' '; });
    std::cout << '\n';

    auto func1 = [](int n) { return n + 4; };
    std::cout << "func1: " << func1(6) << '\n';

    std::function<int(int)> func2 = [](int n) { return n + 4; };
    std::cout << "func2: " << func2(6) << '\n';
}
$ clang -std=c++11 1.cpp -lstdc++ && ./a.out 
c: 5 6 7 
func1: 10
func2: 10

參考資料。

  1. djinni helloworld
  2. Dropbox經(jīng)驗談:iOS和Android的C++跨平臺開發(fā)
  3. Facebook應(yīng)用Moments使用C++實現(xiàn)跨平臺代碼共享
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末讼溺,一起剝皮案震驚了整個濱河市楣号,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖炫狱,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藻懒,死亡現(xiàn)場離奇詭異,居然都是意外死亡视译,警方通過查閱死者的電腦和手機嬉荆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酷含,“玉大人鄙早,你說我怎么就攤上這事∫窝牵” “怎么了限番?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呀舔。 經(jīng)常有香客問我弥虐,道長,這世上最難降的妖魔是什么媚赖? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任霜瘪,我火速辦了婚禮,結(jié)果婚禮上惧磺,老公的妹妹穿的比我還像新娘粥庄。我一直安慰自己,他們只是感情好豺妓,可當我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著布讹,像睡著了一般琳拭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上描验,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天白嘁,我揣著相機與錄音,去河邊找鬼膘流。 笑死絮缅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的呼股。 我是一名探鬼主播耕魄,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼彭谁!你這毒婦竟也來了吸奴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎则奥,沒想到半個月后考润,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡读处,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年糊治,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罚舱。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡井辜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出馆匿,到底是詐尸還是另有隱情抑胎,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布渐北,位于F島的核電站阿逃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赃蛛。R本人自食惡果不足惜恃锉,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呕臂。 院中可真熱鬧破托,春花似錦、人聲如沸歧蒋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谜洽。三九已至萝映,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間阐虚,已是汗流浹背序臂。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留实束,地道東北人奥秆。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像咸灿,于是被迫代替她去往敵國和親构订。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,514評論 2 348

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

  • Swift版本點擊這里歡迎加入QQ群交流: 594119878最新更新日期:18-09-17 About A cu...
    ylgwhyh閱讀 25,333評論 7 249
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫分尸、插件锦聊、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,064評論 4 62
  • 端口 netstat -anp|grep 80 #查看端口占用(進程)netstat -anp|more #查看...
    劉計計計閱讀 406評論 0 0
  • 是誰 像晨曦的陽光般 給我?guī)砉饬?是誰 像烈日的太陽 把我曬傷 溫柔的力量 強大 足以喚醒 所有沉睡的黑暗 烈日...
    蔡振源閱讀 188評論 0 2
  • 架構(gòu)師之路--服務(wù)器集群搭建、管理箩绍、與快速部署 什么是集群孔庭? 集群,是一組獨立的計算機系統(tǒng)構(gòu)成一個松耦合的多處理器...
    劉宇龍閱讀 204評論 0 0