iOS - OC & C++ 混编

2019-01-02

React Native 源码时发现里面很多实现都是通过 C++ 实现,为了看懂它的实现,对 C++ 和 OC 的混编进行一番探索。

1. OC & C++ 混编

1.1 文件类型

  • .h : 头文件,可以同时当做 OC 以及 C++ 的头文件
  • .m : OC 的实现文件
  • .hpp : C++ 的头文件
  • .cpp : C++ 的实现文件
  • .mm : OC & C++ 混编文件,该文件中可以在 OC 代码中使用 C++

1.2 一个简单混编代码

C++ 代码:

// ===================== Person.h =====================
// ===================== C++ 头文件 =====================
#ifndef Person_h
#define Person_h

#include <stdio.h>
#include <string.h>
#include <iostream>

#endif /* Person_h */

namespace cppdemo {
    
    class Person {
    public:
        ~Person();
        int age;
        void sayHello();
        // 默认构造函数
        Person();
        // 带参数的构造函数
        Person(int _age, std::string _name);
    private:
        std::string name;
    };
}

// ===================== Person.cpp =====================
// ===================== C++ 实现文件 =====================
#include "Person.h"

namespace cppdemo {
    
    // '::' 表示是 class Person 中的方法
    // class Person 中的默认构造函数
    Person::Person() {
        this->age = 10;
        this->name = "Tommy";
    }
    
    Person::Person(int _age, std::string _name) {
        this->age = _age;
        this->name = _name;
    }
    
    // 以上方法等同与下面这种实现
//    Person::Person(int _age, std::string _name): age(_age), name(_name) {
//
//    }
    
    void Person::sayHello() {
        std::cout << name << " hello age: " << age << "\n";
    }
}

OC & C++ 混编代码:

// ===================== Family.h =====================
// ===================== OC 头文件 =====================
#import <Foundation/Foundation.h>

@interface Family : NSObject

- (void)addTommy;

- (void)addLily;

- (void)everyBodySayHello;

@end

// ===================== Family.mm =====================
// ===================== OC & C++ 混编文件 =====================
#import "Family.h"
#import "Person.h"

using namespace cppdemo;

@implementation Family {
    cppdemo::Person *Tommy;
    cppdemo::Person *Lily;
}

- (void)addTommy {
    Tommy = new cppdemo::Person();
}

- (void)addLily {
    int age = 22;
    NSString *name = @"Lily";
    Lily = new cppdemo::Person(age, [name UTF8String]);
}

- (void)everyBodySayHello {
    Tommy->sayHello();
    Lily->sayHello();
}

@end

以上就是一个在 OC 中使用 C++ 的简单实现,接下来就可以直接通过 OC 代码调用 C++ 文件中的方法、类等。可以通过以下代码测试一下

Family *family = [Family new];
[family addTommy];
[family addLily];
[family everyBodySayHello];

可以在控制台看到输出

Tommy hello age: 10
Lily hello age: 22

2. React Native 源码简单解读

为了更好的学习 C++ 在 iOS 中的使用,我针对 React Native 中的 RCTCxxBridge.mmInstance.cppNativeToJsBridge.cppJSCExecutor.cpp 文件简单解读。不对具体实现进行说明,只为了说明 RN 原生 调用 JS 的过程中如何进行 OC 和 C++ 的混编的。涉及到的一些 C++ 类型在下面 3. 一些 C++ 类型介绍 有说明,如 std::shared_ptrstd::move

  1. 先不管前面 RN 都做了什么,我们以方法 - (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync 作为入口,接下来是一个 RN 原生 调用 JS 的过程
// RCTCxxBridge.mm
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
  // ......

#if RCT_DEV
  if (self.devSettings.isHotLoadingAvailable && self.devSettings.isHotLoadingEnabled) {
    // ....
    [self enqueueJSCall:@"HMRClient"
                 method:@"enable"
                   args:@[@"ios", path, host, RCTNullIfNil(port)]
             completion:NULL];  }
#endif
}
  1. 接下来调用了 enqueueJSCall
// RCTCxxBridge.mm
- (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion
{
  //.....

    if (strongSelf->_reactInstance) {
      strongSelf->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],
                                             convertIdToFollyDynamic(args ?: @[]));
    	// .....
    }
}
  1. 我们看以下 strongSelf->_reactInstance 的实现,可以看出它是一个 C++ Instance 类,实现在文件 Instance.cpp 中。这样就在 OC 中调用 C++ 的代码。代码中 std::shared_ptr<Instance> 是声明类型用的,具体意思可以看下面 3. 一些 C++ 类型介绍
// RCTCxxBridge.mm
@implementation RCTCxxBridge
{
  // .......
  // This is uniquely owned, but weak_ptr is used.
  std::shared_ptr<Instance> _reactInstance;
}

- (void)start {
    // ......
    // This doesn't really do anything.  The real work happens in initializeBridge.
  	_reactInstance.reset(new Instance);
    // ......
}

  1. 接着上面 (2) 看 _reactInstance->callJSFunction 的实现,下面 std::move 的说明也可以看 3.3
// Instance.cpp
void Instance::callJSFunction(std::string &&module, std::string &&method,
                              folly::dynamic &&params) {
  callback_->incrementPendingJSCalls();
  nativeToJsBridge_->callFunction(std::move(module), std::move(method),
                                  std::move(params));
}
  1. 接着调用了 nativeToJsBridge_->callFunction 方法
// NativeToJsBridge.cpp
void NativeToJsBridge::callFunction(
    std::string&& module,
    std::string&& method,
    folly::dynamic&& arguments) {
  // ......	
  runOnExecutorQueue([this, module = std::move(module), method = std::move(method), arguments = std::move(arguments), systraceCookie](JSExecutor* executor) {
      // ......
      
      // This is safe because we are running on the executor's thread: it won't
      // destruct until after it's been unregistered (which we check above) and
      // that will happen on this thread
      executor->callFunction(module, method, arguments);
    });
}
  1. 接下来调用的是 JSExecutor 的的 callFunction 方法
// JSExecutor.cpp
void JSCExecutor::callFunction(
    const std::string& moduleId,
    const std::string& methodId,
    const folly::dynamic& arguments) {
  	// ......
    
      return m_callFunctionReturnFlushedQueueJS->callAsFunction(
          {Value(m_context, String::createExpectingAscii(m_context, moduleId)),
           Value(m_context, String::createExpectingAscii(m_context, methodId)),
           Value::fromDynamic(m_context, std::move(arguments))});
    
    // ......
}
  1. 接着调用到以下代码。在下面代码中已经可以看到了 JSValueRefJSObjectRef ,看到这些以 Ref 结尾的类型是不是很熟悉。 std::initializer_list 也在 3.4 中有说明
// Value.cpp
Value Object::callAsFunction(std::initializer_list<JSValueRef> args) const {
  return callAsFunction(nullptr, args.size(), args.begin());
}

// 这是一个箭头
//     ||  
//     ||
// 	  \  /
//     \/

Value Object::callAsFunction(JSObjectRef thisObj, int nArgs, const JSValueRef args[]) const {
  JSValueRef exn;
  JSValueRef result = JSC_JSObjectCallAsFunction(m_context, m_obj, thisObj, nArgs, args, &exn);
  // ......
}
  1. JSC_JSObjectCallAsFunction 方法走到最后最后,又走到了我们 JavaScriptCore.framework 中了
// <JavaScriptCore/JSObjectRef>
/*!
@function
@abstract Calls an object as a function.
@param ctx The execution context to use.
@param object The JSObject to call as a function.
@param thisObject The object to use as "this," or NULL to use the global object as "this."
@param argumentCount An integer count of the number of arguments in arguments.
@param arguments A JSValue array of arguments to pass to the function. Pass NULL if argumentCount is 0.
@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception.
@result The JSValue that results from calling object as a function, or NULL if an exception is thrown or object is not a function.
*/
JS_EXPORT JSValueRef JSObjectCallAsFunction(JSContextRef ctx, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception);

<JavaScriptCore/JavaScriptCore.h> 中头文件的定义,可以发现 JavaScriptCore.framework 提供了 C 语言的接口,同时也提供了 OC 的接口。

// <JavaScriptCore/JavaScriptCore.h
#ifndef JavaScriptCore_h
#define JavaScriptCore_h

// 底层 C 语言接口
#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/JSStringRefCF.h>

#if defined(__OBJC__) && JSC_OBJC_API_ENABLED

// OC 对底层 C 语言接口的再次封装
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

#endif

#endif /* JavaScriptCore_h */

3. 一些 C++ 类型介绍

以下 C++ 类型介绍来自 cppreference

3.1 shared_ptr

std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。下列情况之一出现时销毁对象并解分配其内存:

  • 最后剩下的占有对象的 shared_ptr 被销毁;
  • 最后剩下的占有对象的 shared_ptr 被通过 operator=reset() 赋值为另一指针。

delete 表达式或在构造期间提供给 shared_ptr 的定制删除器销毁对象。

struct Derived: public Base
{
    Derived() { std::cout << "  Derived::Derived()\n"; }
    ~Derived() { std::cout << "  Derived::~Derived()\n"; }
};

int main()
{
    std::shared_ptr<Base> p = std::make_shared<Derived>();
    p.reset(); // 从 main 释放所有权
}

3.2 unique_ptr

std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针。

在下列两者之一发生时用关联的删除器释放对象:

  • 销毁了管理的 unique_ptr 对象
  • 通过 operator=reset() 赋值另一指针给管理的 unique_ptr 对象。

通过调用 get_deleter()(ptr) ,用潜在为用户提供的删除器释放对象。默认删除器用 delete 运算符,它销毁对象并解分配内存。

struct D
{
    D() { std::cout << "D::D\n";  }
    ~D() { std::cout << "D::~D\n";  }
    void bar() override { std::cout << "D::bar\n";  }
};
 
// 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
    p->bar();
    return p;
}
  
int main()
{
  std::cout << "unique ownership semantics demo\n";
  {
      auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
      auto q = pass_through(std::move(p)); 
      assert(!p); // 现在 p 不占有任何内容并保有空指针
      q->bar();   // 而 q 占有 D 对象
  } // ~D 调用于此
}

3.3 std::move

std::move 用于指示对象 t 可以“被移动”,即允许从 t 到另一对象的有效率的资源传递。

特别是, std::move 生成标识其参数 t亡值表达式。它准确地等价于到右值引用类型的 static_cast 。

int main()
{
    std::string str = "Hello";
    std::vector<std::string> v;
 
    // 使用 push_back(const T&) 重载,
    // 表示我们将带来复制 str 的成本
    v.push_back(str);
 
    // 使用右值引用 push_back(T&&) 重载,
    // 表示不复制字符串;而是
    // str 的内容被移动进 vector
    // 这个开销比较低,但也意味着 str 现在可能为空。
    v.push_back(std::move(str));
}

3.4 std::initializer_list

std::initializer_list<T> 类型对象是一个访问 const T 类型对象数组的轻量代理对象。

std::initializer_list 对象在这些时候自动构造:

  • 花括号初始化器列表列表初始化一个对象,其中对应构造函数接受一个 std::initializer_list 参数
  • 花括号初始化器列表赋值的右运算数,或函数调用参数,而对应的赋值运算符/函数接受 std::initializer_list 参数
  • 绑定花括号初始化器列表auto ,包括在范围 for 循环

initializer_list 可由一对指针或指针与其长度实现。复制一个 std::initializer_list 不会复制其底层对象。

template <class T>
struct S {
    std::vector<T> v;
    S(std::initializer_list<T> l) : v(l) {
         std::cout << "constructed with a " << l.size() << "-element list\n";
    }
    void append(std::initializer_list<T> l) {
        v.insert(v.end(), l.begin(), l.end());
    }
    std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};  // 在 return 语句中复制列表初始化
                                   // 这不使用 std::initializer_list
    }
};
 
template <typename T>
void templated_fn(T) {}
 
int main()
{
    S<int> s = {1, 2, 3, 4, 5}; // 复制初始化
    s.append({6, 7, 8});      // 函数调用中的列表初始化
 
    for (int x : {-1, -2, -3}) // auto 的规则令此带范围 for 工作
        std::cout << x << ' ';
 
    auto al = {10, 11, 12};   // auto 的特殊规则
}