djinni簡(jiǎn)析

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

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

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

Paste_Image.png
Paste_Image.png

腳本

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

../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++實(shí)現(xiàn),向Objective-C暴露一個(gè)WLALoginSDK類,但是它主要調(diào)用LoginProvider干活。LoginProvider通過C++定義了接口,但是通過WLALoginProviderImpl這個(gè)Objective-C類來實(shí)現(xiàn)具體的邏輯,比如OAuth的認(rèn)證流程采用OpenID下面的AppAuth-iOS和AppAuth-Android來完成,這些庫并不是C++寫的。

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

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

C++

不管+c還是+o +j,都會(huì)生成一個(gè)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,都會(huì)生成一個(gè)Objective-C++的類,文件名是xxx+private.hxxx+private.mm。Objective-C++類有兩個(gè)主要的作用就是toCpp和fromCpp,讓C++對(duì)象和Objective-C對(duì)象可以互相找到彼此。針對(duì)+c+o +j生成的代碼有所不同。下面分兩種情況分析一下。

+c

主要是完善WLALoginSDK類,它持有一個(gè)::wla_gen::LoginSDK純虛類的實(shí)現(xiàn),干活都是這個(gè)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則實(shí)現(xiàn)了::wla_gen::LoginProvider::djinni::ObjcProxyCache::Handle<ObjcType>,一看就知道要甩鍋給一個(gè)Objective-C對(duì)象。

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做各種事情。這個(gè)地方可以寫一些平臺(tái)同樣的邏輯,比如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來實(shí)現(xiàn)具體的邏輯,所以要有一個(gè)實(shí)現(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認(rèn)證
}

djinni不足之處

djinni無法表達(dá)每個(gè)端專有的特性,具體到iOS端,有如下不足。

  1. 無法表達(dá)nonull、nullable。對(duì)于普通屬性統(tǒng)統(tǒng)使用nonull,對(duì)于callback則使用nullable。
  2. 無法表達(dá)協(xié)議的@optional。由于IDL往往是各端接口的超集,iOS只會(huì)實(shí)現(xiàn)其中某些接口,這樣會(huì)導(dǎo)致很多警告。
  3. 跟所有IDL一樣,無法使用各端特有的數(shù)據(jù)類型,無法使用泛型。
  4. 以上缺點(diǎn)都能忍,callback的問題實(shí)在是很難忍受,Objective-C和C++的Callback寫起來都很麻煩。

Objective-C callback

所有+o +j的callback都會(huì)變成協(xié)議,需要一個(gè)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

//使用起來一點(diǎn)都沒有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包裝了一個(gè)lambda閉包。反正現(xiàn)在C++ 11的代碼鬼都看不懂,貼一個(gè)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)驗(yàn)談:iOS和Android的C++跨平臺(tái)開發(fā)
  3. Facebook應(yīng)用Moments使用C++實(shí)現(xiàn)跨平臺(tái)代碼共享
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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