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庫作喘。
腳本
從腳本可以看出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.h
和xxx+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端,有如下不足掀序。
- 無法表達
nonull
帆焕、nullable
。對于普通屬性統(tǒng)統(tǒng)使用nonull
不恭,對于callback則使用nullable
叶雹。 - 無法表達協(xié)議的
@optional
。由于IDL往往是各端接口的超集换吧,iOS只會實現(xiàn)其中某些接口折晦,這樣會導致很多警告。 - 跟所有IDL一樣沾瓦,無法使用各端特有的數(shù)據(jù)類型满着,無法使用泛型。
- 以上缺點都能忍贯莺,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