Kittinun Vantasin
1. Suck it up and move on as there is no way to solve this
2. This must be put to an END!, we must at least DO SOMETHING!
1. Use latest and greatest toolchains from both platforms (using Xcode for iOS, Android studio for Android)
2. Want our apps to be natively looking (using NavigationBar for iOS, Toolbar for Android etc....)
3. Embrace latest languages/patterns/ideas each OS has to offer (using Swift for iOS, Kotlin for Android)
1. Find common sane language, .... candidates are C++, C, Assembly
2. Build a core library that share among iOS/Android apps
3. Focus to automation, generate tedious stuff by using tools
1. Design portion of code that needs to be shared e.g. networking, database
4. Build core code into (.a) - static library (iOS) and (.so) shared library (Android)
2. Write that code in C++
3. Expose header file so application can call into
5. Write UI code then call into C++ code
App Layer (Swift/Objc, Java/Kotlin)
Bridging Layer (ObjC++, Java - JNI)
Core Layer (C++)
@interface Greeting : NSObject
- (NSString *)greet:(NSString *)name;
@end
@implementation Greeting : NSObject
- (NSString *)greet:(NSString *)name {
std::string nameInStr = [name UTF8String];
hello::greeting g(nameInStr);
return [NSString stringWithUTF8String:g.greet().c_str()];
}
@end
extern "C"
JNIEXPORT jstring JNICALL Java_com_github_kittinunf_greeting_greet(
JNIEnv * env, jobject thiz, jstring arg) {
jclass clazz = env->GetObjectClass(thiz);
jmethodID method = env->GetMethodID(clazz, "getGreetCpp", “()J");
jlong cpp_long = env->CallLongMethod(thiz, method, arg);
JniDemo * cpp_obj = reinterpret_cast<hello::greeting*>(cpp_long);
const char * arg_cstr = env->GetStringUTFChars(arg, nullptr);
std::string result = cpp_obj->greet(arg_cstr);
env->ReleaseStringUTFChars(arg, arg_cstr);
jstring jresult = env->NewStringUTF(result.c_str());
return jresult;
}
If you like what you see ...
demo = interface +c, +j, +o {
foo(arg: string): string;
}
Bridging
ObjC++
Java-JNI
C++
demo = interface +c +j +o {
foo1(name: string);
foo2(): list<f32>;
foo3(i: i16);
}
Djinni IDL
class demo {
public:
virtual ~demo() {}
virtual void foo1(const std::string & name) = 0;
virtual std::vector<float> foo2() = 0;
virtual void foo3(int16_t i) = 0;
};
C++
@protocol XYZDemo
- (void)foo1:(nonnull NSString *)name;
- (nonnull NSArray<NSNumber *> *)foo2;
- (void)foo3:(int16_t)i;
@end
ObjC++
import java.util.ArrayList;
public abstract class Demo {
public abstract void foo1(String name);
public abstract ArrayList<Float> foo2();
public abstract void foo3(short i);
}
Java
Djinni IDL | ObjC++ | Java |
---|---|---|
bool | BOOL | boolean |
i8, i16, i32, i64 | int<8|16|32|64>_t | byte/short/int/long |
string | NSString | String |
binary | NSData | byte[] |
date | NSDate | Date |
list<T> | NSArray | ArrayList |
map<K,V> | NSDictionary | HashMap<K,V> |
optional<T> | __nullable T | T |
interface | class | class |
record | class | class |
{
"targets":
[
{
"target_name":"lib<target_name>",
"type":"static_library",
"sources":[
<source_files>
],
"include_dirs":[
<include_files>
],
"dependencies":[
<dependencies, e.g. other gyp project>
],
"libraries":[
<other_libraries>
],
"defines":[
<defines>
],
"cflags_cc":[
<compiler_flags>
]
}
]
}
C++
ObjC++
Java-JNI
Djinni
Gyp
Swift
Kotlin
iOS App
Static Library (.a)
Android App
Shared Library (.so)
Core
Xcode
Android Studio
Cpp มันสวยงามมากเลยนะครับ
"
Bjarne Stroustrup
"
ไม่ได้กล่าวไว้
Swift | C++ | |
---|---|---|
Variable declaration | let i = 5 var ii = 10 |
const auto i = 5 auto ii = 10 |
Loop | for i in arr { } | for (auto i : arr) { } |
Ownership | let f = Foo() | Foo f / auto f = make_shared<foo>() |
Ownership | weak var f: Foo | weak_ptr<foo> f = ... |
Block | let f = { print() } | auto f = []() { printf() } |
Optional | var f: Foo? | optional<foo> f |
Concurrent | dispatch_async | std::async() |
Initialization | let arr = [1,2,3] | vector<int> v{ 1,2,3 } |
Swift | C++ | |
---|---|---|
map | a.map { $0 * $0 } | transform(a.begin(), a.end(), a.begin(), [](int i) { return i * i } |
filter | a.filter { $0 % 2 == 0 } | remove_if(a.begin(), a.end(), [](int i) { return i % 2 == 0 } |
reduce | a.reduce(0) { ag, i in ag += i } | accumulate(a.begin(), a.end(), 0, plus<int>()) |
Call wrapper forwarding | -* | auto plus10 = bind(plus<int>(), _1, 10) plus10(50) |
* Unless doing it manually
C++
ObjC++
Java-JNI
Djinni
Swift
Kotlin
VM
VM
VIEW
VIEW
MODEL
Let's build Flickr client App
Explore
Able to explore pictures
Search
Able to search pictures by keyword
Map
Able to see where pictures
have been taken with geo tag
Networking : libcurl (curl/curl)
Json : json11 (dropbox/json11)
Database : lmdb (LMDB/lmdb)
Image : Kingfisher (onevcat/Kingfisher)
Reactive Programming : RxSwift (ReactiveX/RxSwift)
imageUrl: String
title: String
id: String
explore_detail_view_data = record {
id: string;
image_url: string;
title: string;
} deriving (eq)
[ExploreDetailViewData]
explore_view_data = record {
error: bool;
message: string;
explores: list<explore_detail_view_data>;
}
One class to represent logic of your page (Controller)
One class(conforms objc protocol) to receive your data (Observer)
One class to represent your view (ViewController)
Djinni declaration
Base class (C++)
Base protocol (ObjC++)
Concrete class (C++)
Class (Swift)
Wrapper(ObjC++)
View Controller
(Swift)
owns
owns
generates
generates
generates
VM
VIEW
own
Update reactively
With help of RxSwift
class explore_controller_observer;
class explore_controller {
public:
virtual ~explore_controller() {}
static std::shared_ptr<explore_controller> create();
virtual void subscribe(const std::shared_ptr
<explore_controller_observer> & observer) = 0;
virtual void unsubscribe() = 0;
virtual void reset() = 0;
virtual void request(int8_t page) = 0;
};
explore_controller_observer = interface +o +j {
on_begin_update();
on_update(view_data: explore_view_data);
on_end_update();
}
explore_controller = interface +c {
static create(): explore_controller;
subscribe(observer: explore_controller_observer);
unsubscribe();
reset();
request(page: i8);
}
@protocol CPExploreControllerObserver
- (void)onBeginUpdate;
- (void)onUpdate:(nonnull CPExploreViewData *)viewData;
- (void)onEndUpdate;
@end
public abstract class ExploreControllerObserver {
public abstract void onBeginUpdate();
public abstract void onUpdate(ExploreViewData viewData);
public abstract void onEndUpdate();
}
C++ can be a practical way of mobile development
Shared codebase is possible with no compromisation
Good organization/architecture of code is vital
The success of this strategy depends on good understanding of both platforms
C++ is a beasts, so powerful but unforgiving
Logging on Android is not smooth, debugging onto C++ code on Android is horrendous
Communication between teams is key
Revamp repo/branch strategy is needed
kittinunf/Cookpit
taskworld/flowcpp