djinni简析

djinni是Dropbox开源的一个库,它会根据IDL定义生成相应的接口和很多胶水代码,可以做到Mac、PC、iOS、Android四个端共享接口和C++基础代码。

本文仅从iOS的角度分析一下怎么djinni的基础使用方法。djinni的调用路径是C++->ObjectiveC++->Objective-C,所以从每种语言的层面做一些分析。

整个的关系图如下所示。调用路径是WLALoginSDK->cppRef(WLALoginSDKImpl)->djinni_generated::LoginProvider->ObjcRef(WLALoginProviderImpl)。如果未来有C++实现的OAuth 2.0客户端,调用逻辑可以做很多简化,不需要一个provider去调用AppAuth库。

djinni简析_第1张图片
Paste_Image.png
djinni简析_第2张图片
Paste_Image.png

脚本

从脚本可以看出C++代码的namespace是wla_gen,而Objective-C++代码的namespace是djinni_generated。Objective-C相关的类和协议都会加上objc-type-prefix前缀,也就是脚本里面配置的WLA。记住这些规律,有利于分析每个类的角色。

../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++实现,向Objective-C暴露一个WLALoginSDK类,但是它主要调用LoginProvider干活。LoginProvider通过C++定义了接口,但是通过WLALoginProviderImpl这个Objective-C类来实现具体的逻辑,比如OAuth的认证流程采用OpenID下面的AppAuth-iOS和AppAuth-Android来完成,这些库并不是C++写的。

//+c 表示用C++实现逻辑,暴露给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 表示要用各端的代码实现逻辑
// 会在Objective-C生成一个 @protocol WLALoginProvider,需要有一个Objective-C实现类。
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 sharedInstance();

    virtual void setLoginProvider(const std::shared_ptr & 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纯虚类的实现,干活都是这个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> _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)provider {
    try {
        _cppRefHandle.get()->setLoginProvider(::djinni_generated::LoginProvider::toCpp(provider));
    } DJINNI_TRANSLATE_EXCEPTIONS()
}

+o +j

而LoginProvider则实现了::wla_gen::LoginProvider::djinni::ObjcProxyCache::Handle,一看就知道要甩锅给一个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;

    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
{
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++写的,主要是调LoginProvider做各种事情。这个地方可以写一些平台同样的逻辑,比如token的存取等。

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

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

Objective-C

LoginProvider要Objective-C来实现具体的逻辑,所以要有一个实现WLALoginProvider协议的类。

@interface WLALoginProviderImpl : NSObject 

@end

@interface WLALoginProviderImpl ()

@property(nonatomic, strong, nullable) id currentAuthorizationFlow;

@end

@implementation WLALoginProviderImpl

- (void)login:(nullable id)callback {
    //使用AppAuth-iOS发起OAuth 2.0认证
}

djinni不足之处

djinni无法表达每个端专有的特性,具体到iOS端,有如下不足。

  1. 无法表达nonullnullable。对于普通属性统统使用nonull,对于callback则使用nullable
  2. 无法表达协议的@optional。由于IDL往往是各端接口的超集,iOS只会实现其中某些接口,这样会导致很多警告。
  3. 跟所有IDL一样,无法使用各端特有的数据类型,无法使用泛型。
  4. 以上缺点都能忍,callback的问题实在是很难忍受,Objective-C和C++的Callback写起来都很麻烦。

Objective-C callback

所有+o +j的callback都会变成协议,需要一个Objective-C包装类,使用起来非常之麻烦。

@interface WLALoginCallbackImpl : NSObject 

- (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构造出来,不算方便,但是比起Objective-C还是要好一些的。

void LoginSDKImp::login(const std::shared_ptr & callback)
{
    if (_provider) {
        std::weak_ptr weak_this = shared_from_this();
        auto provider_callback = wla::makeCallback([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闭包。反正现在C++ 11的代码鬼都看不懂,贴一个lambda闭包的例子吧。

#include 
#include 
#include 
#include 

int main()
{
    std::vector 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 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经验谈:iOS和Android的C++跨平台开发
  3. Facebook应用Moments使用C++实现跨平台代码共享

你可能感兴趣的:(djinni简析)