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庫。
腳本
從腳本可以看出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.h
和xxx+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端,有如下不足。
- 無法表達(dá)
nonull
、nullable
。對(duì)于普通屬性統(tǒng)統(tǒng)使用nonull
,對(duì)于callback則使用nullable
。 - 無法表達(dá)協(xié)議的
@optional
。由于IDL往往是各端接口的超集,iOS只會(huì)實(shí)現(xiàn)其中某些接口,這樣會(huì)導(dǎo)致很多警告。 - 跟所有IDL一樣,無法使用各端特有的數(shù)據(jù)類型,無法使用泛型。
- 以上缺點(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