混编ObjectiveC++

迁移一批老文章到掘金html

最近有点炒冷饭的嫌疑,不过确实之前没有Git Or Blog的习惯,因此不少工做上的技术分享就存留在了电脑的文档里,如今仍是想从新整理一下,再分享出来。java

混编C++也是之前工做中须要用到的,因而被我炒冷饭翻了出来,不过确实有从新整理了一下android

为何要使用C++混编

1)须要使用工具库或者源码是C++的

各个平台,各类语言,都会有不少开源的工具和库文件方便你们学习和使用,但如C与C++这版经典的语言,不少底层的,算法型的库都是用C++实现的,尤为是不少人脸识别,图形滤镜算法,视频处理算法,甚至底层图形渲染OpenGLios

2)C++执行效率快

你们都知道C++的执行效率快,因此在高复杂度高算法层面的开发内容里,大多都选择使用C++来完成,我是作客户端的,虽然不像作机器学习,大数据处理等在工做中须要普遍运用高效算法,但上面提到的人脸识别,图形滤镜算法,甚至视频处理,还有不少游戏内部须要的游戏AI,都是有可能运用在咱们熟知的客户端开发之中c++

3)跨平台

C++是编译型跨平台的,C++的代码编译出来的二进制文件能够在android,iOS,甚至WP上均可以正常运行,可谓是真·跨平台web

说到跨平台,确定很多人提起H5跨平台呀,ReactNative跨平台呀,这类一般属于解释型跨平台,约定好一种脚本语言,底层辅助以各平台的基础实现,甚至底层就是借助C++实现,经过底层解读脚本语言,在运行时进行解释实现逻辑,就比如webkit做为浏览器的核心,JavaScriptCore做为RN的核心,虽然开发中使用了js进行写代码,可是究其本质仍是在运行时解释js在进行native执行的。js代码并不参与编译,这类跨平台在编译时参与编译的,正是那套语法解释器+NA底层代码,他们或多或少仍是经过C++实现的objective-c

咱们使用C++作逻辑的缘由

咱们作客户端,核心模块使用C++的缘由其实就是出自(2)(3)两点,由于咱们的业务涉及极其复杂的文字排版,而不管是iOS平台仍是安卓平台,基础排版是很难知足中文甚至我大天朝独有政治要求的,想要实现势必要在每一个平台上分别封装一套极度复杂的排版策略控制,所以咱们放弃了使用CoreText的基础排版API(安卓上用啥排版不知道),而选择用C++实现一套通用于两个平台的排版策略,固然在排版速度效率上也是要很高要求的算法

ObjectiveC 与 C++ 的共同点

在iOS开发之中,OC代码与C++代码能够完美的融合在一块,何谓完美?你甚至能够上一行刚敲完[NSArray objectAtIndex:xx](OC代码)下一行就使用STL构建一个C++的List数组(C++代码),他们之间能够完美编译,生成正常的程序,而且还能够单步debug,随时跟进一层一层的方法,刚刚单步跳出一个OC的messageSend,立刻就能够单步跟进一个C++ Class的function,这之间没有一点障碍,全部变量,指针,结构体,数据,均可以任意查看,两者之间畅通无阻数组

向下彻底兼容C是他们的共同点和纽带

为何会这样?由于C++与OC都彻底向下兼容C 全部的OC对象,虽然有ARC辅助内存管理,但他本质上仍是一个void *,同理C++也同样是void *,OC之因此调用函数叫作发送消息,是由于封装了层独有的runtime机制(这机制仍是C的),但归根结底每一个函数实体依然是一个IMP,依然是一个函数指针,这一点和C++也同样,因此他们之间的衔接才会如此通畅xcode

其余混编状况可就没那么容易了

  • android混编C++,恩很麻烦,只能先编译成so,两个环境若是要交互,要先手写一套JNI,把C++环境下的数据和java环境下的数据进行手动转换,而且断点调试无法断点进入so内,想要debug调试,必须靠fwrite写log到本地磁盘调试╮(╯_╰)╭

  • 我之前搞过游戏,作过C++内混编lua脚本,这俩互通更蛋疼,虽然lua的解释器底层是用C写的,可是全部的内存都是lua解释器(或者叫虚拟机)内的数据,所以若是两者要互通,也要写一个通道来交换数据,这个交换数据,就是经过超级烦琐的数据对齐,压栈,出栈来互通。

  • 前一阵子也学习了一些JSPatch,他其实能够看作是js代码混编Oc的模范工程,同lua同样,整个js的运行环境也是依赖于JavaScriptCore提供的一套JS虚拟机来执行,他有着本身的上下文JSContext,虽然说简单的通用数据,字符串,数组,字典,被JavaScriptCore自动的执行完了转换,但一旦须要两个环境交换独有数据类型,例如js里面的function,例如oc里面的自定义NSObject,那么就须要JSValue这个对象起到转换和传递的做用

ObjectiveC如何混编C++

  • 想要建立一个纯C++类,你只须要建立.h开头和.cpp开头的文件,直接导入工程就好,若是须要使用一些C++的标准库,能够直接从Xcode导入libstdC++

  • 若是你想建立一个能即识别C++又识别OC的对象,只须要照常建立一个.h 文件和.m文件,而后将.m文件重命名成.mm文件,就是告诉编译器,这个文件能够进行混编 — ObjectiveC++(名字是否是有点酷)

  • 若是你想建立一个纯OC类,那这还须要用我说么?

如今你的工程里,能够有这三种文件存在,基本上就能够知足咱们的混编需求了。

怎么样是否是很想赶快试试了?

例子:在一个OC环境里调用C++

个人例子会一步一步来,甚至有的步骤中多是错误的代码,给你们展现完错误的代码后,进行说明,再放上正确的代码,

代码也不全是完整代码

CppObject.h C++的头文件 .cpp文件留空,先不写逻辑

#include <string>
class CppObject {
public:
    void ExampleMethod(const std::string& str){};
    // constructor, destructor, other members, etc.
};

复制代码

OCObject.h OC的头文件 .m文件先改成.mm,但先不写逻辑

#import <Foundation/Foundation.h>
//#import "CppObject.h"

@interface OcObject : NSObject {
    CppObject* wrapped;
}

@property CppObject* wrapped2;

- (void)exampleMethodWithString:(NSString*)str;
// other wrapped methods and properties
@end

复制代码

头文件准备完毕,实现文件,我先不写逻辑,先跑一下看看会有什么问题?

跑完了之后会编译报错,报错的缘由很简单,你在OCObject.h中引用了C++的头文件,xcode不认识,没法编译经过。

咦?刚刚不是说好了C++和OC无缝互通了么,这咋又不认识了?缘由很简单,咱们经过修改.m为.mm文件,能让编译器xcode知道这是一个混编文件,可是我可没说修改.h为.hh文件哟,是这样的,对于xcode来讲,能够认识.mm的混编语法,可是不认识.h文件中的混编语法,若是.h全都是C++的写法,没有问题,若是.h全都是OC的写法,没有问题,若是.h里面有C++又有OC?那妥妥的有问题(.h中引入的其余头文件也算在内)

怎么处理呢?两个办法

  • 不在.h里写混编了,那我移到.mm里呗~~~

  • 不让我写c++?ok,我写C,反正写C是没错的,因此老子写void *id

这里的例子我先写到.mm文件里

#import "OcObject.h"
#import "CppObject.h"
@interface OcObject () {
    CppObject* wrapped;
}
@end

@implementation OcObject

- (void)exampleMethodWithString:(NSString*)str
{
    // NOTE: if str is nil this will produce an empty C++ string
    // instead of dereferencing the NULL pointer from UTF8String.
    std::string cpp_str([str UTF8String], [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
    wrapped->ExampleMethod(cpp_str);
}
复制代码

这不~妥了没问题了~,咱们再去补上CPP文件中的函数实现,随便写个printf(),输出个string,例子就完成了

例子:在一个C++环境里调用OC

首先咱们要打造一个C++的环境

--AntiCppObject.h

#include <iostream>

class AntiCppObject {

public:
    AntiCppObject();
    void ExampleMethod(const std::string& str){};
    // constructor, destructor, other members, etc.
};


--AntiCppObject.cpp

#include "AntiCppObject.h"

AntiCppObject::AntiCppObject()
{
    
}
复制代码

而后咱们再准备一个OC类接入C++,m文件我就不补充完了,随便写个NSLog就好

--AntiOcObject.h

#import <Foundation/Foundation.h>

@interface AntiOcObject : NSObject

- (void)function;

@end
复制代码

如今打算接入C++环境了,首先先把.CPP改为.mm文件,妥妥哒

而后修改头文件

--AntiCppObject.h
#import "AntiOcObject.h"
class AntiCppObject {
    AntiOcObject* AntiOc;
public:
    AntiCppObject();
    void ExampleMethod(const std::string& str){};
    // constructor, destructor, other members, etc.
};
复制代码

通过了刚才的例子,看到这应该立马反应过来,这不对,头文件不能混编,会报错的。那应该怎么作呢?

作法仍是上面提到的,要么void *,要么想办法把定义写在.mm文件里,老规矩,void *先不提,咱们先在.h中写个结构体,藏起来那个oc的对象,在mm文件中进行声明

--AntiCppObject.h

#include <iostream>
struct sthStruct;
class AntiCppObject {
    sthStruct* sth;
public:
    AntiCppObject();
    void function();
    // constructor, destructor, other members, etc.
};

---AntiCppObject.cpp

#include "AntiCppObject.h"
#import "AntiOcObject.h"

struct sthStruct {
    AntiOcObject* oc;
};

AntiCppObject::AntiCppObject()
{
    AntiOcObject* t =[[AntiOcObject alloc]init];
    sth = new sthStruct;
    sth->oc = t;
}

void AntiCppObject::function()
{
    [this->sth->oc function];
}
复制代码

你看这样就实现了在C++中调用OC

ObjectiveC++混编注意事项

  • 只须要将.m文件重命名成.mm文件,就是告诉编译器,这个文件能够进行混编 — ObjectiveC++
  • 在一个项目里使用两种语言,就要尽量的把这两种语言分开,尽管你能够一口气将全部的文件重命名,可是两种语言差别性仍是很大,混乱使用,处理起来会很困难
  • header文件没有后缀名变化,没有.hh文件^_^。因此咱们要保持头文件的整洁,将混编代码从头文件移出到mm文件中,保证头文件要么是纯正C++,要么是纯正OC,(固然,有C是绝对没问题的)
  • Objective-C向下彻底兼容C,C++也是,有时候也能够灵活的使用void *指针,当作桥梁,来回在两个环境间传递(上面的例子没有体现)

当心你的内存

  • 按着以前的原则,C++和OC两部分尽可能区分开,各自在各自的独立区域内维护好本身的内存,Objective-C能够是arc也可mrc,C++开发者自行管理内存
  • 在.mm文件中,OC环境中在init 和dealloc中对C++类进行 new 和 delete操做
  • 在.mm文件中,在C++环境中构造和析构函数中进行init 和 release操做
--OcObject.mm
-(id)init
{
    self = [super init];
    if (self) {
        wrapped = new CppObject();
    }
    return self;
}

-(void)dealloc
{
    delete wrapped;
}

--AntiCppObject.mm
AntiCppObject::AntiCppObject()
{
    AntiOcObject* t =[[AntiOcObject alloc]init];
    sth = new sthStruct;
    sth->oc = t;
}

AntiCppObject::~AntiCppObject()
{
    if (sth) {
        [sth->oc release];//arc的话,忽略掉这句话不写
    }
    delete sth;
}

复制代码

这个例子告诉咱们什么?

若是咱们经过oc的方式建立出来的,他的内存天然归OC管理,若是是mrc,请使用release,若是是arc,只要置空,天然会自动释放

若是咱们经过C++的方式,构造函数new出来的,那咱们就要手动的使用析构函数就释放他

其实不少事情原理是同样的

  • 咱们在iOS开发使用CF函数的时候,但凡使用CFCreateXX的必定要手动本身调用CFRlease
  • 咱们在编写C++的时候,使用malloc的必定要本身free,使用new的必定要本身delete

id的妙用

刚才的例子中,我虽然频繁提到void *可是并无详细加以说明,神奇的东西应该放在最后

首先说一下id这个很特殊的东西

前面第二个例子,咱们是借助一个结构体struct把oc代码隐藏到.mm文件里,那么咱们能够不借助struct么?固然能够

--AntiCppObject.h
#include <iostream>
struct sthStruct;
class AntiCppObject
{
    id sthoc;
    sthStruct* sth;
public:
    AntiCppObject();
    ~AntiCppObject();
    void function();
    // constructor, destructor, other members, etc.
};

--AntiCppObject.cpp
#include "AntiCppObject.h"
#import "AntiOcObject.h"

struct sthStruct
{
    AntiOcObject* oc;
};

AntiCppObject::AntiCppObject()
{
    AntiOcObject* t =[[AntiOcObject alloc]init];
    sth = new sthStruct;
    sth->oc = t;
    
    sthoc = [[AntiOcObject alloc]init];
}

AntiCppObject::~AntiCppObject()
{
    if (sth) {
        [sth->oc release];
        
        [sthoc release];
    }
    delete sth;
}

void AntiCppObject::function()
{
    [this->sth->oc function];
    [this->sthoc function];
}
复制代码

能够看到这个例子中,那个struct还在,旧的方案仍然保留,可是咱们在头文件里写了一个id类型,xcode编译器在全都是C++代码的.h文件里虽然不认识oc对象,可是实际上是认识id的,咱们借助这个id,就能够不借助struct隐藏oc对象声明了

神奇的void *

终于说到这个void *了,首先咱们写个oc对象,能够持有void *,写个C++也能够持有,甚至咱们不写任何对象,在写一个static的C代码,也能够在一个全局控件保存一个void *对象,正是这个void *对象,能够灵活的组合出各类混编用法

void *是什么?就是指针的最本来形态,利用它咱们能够各类花式的进行混编OC与C++

惟一须要注意的就是id(即oc对象)与void *的转换,要知道arc是有内存管理的,而C++是没有的,若是都一股脑的随便两者转来转去,那内存管理到底该如何自动进行释放?(mrc下两者转换是不须要特别处理的)

所以Arc下两者进行转换常常伴随着一些强转关键字

  • __bridge
  • __bridge_retained
  • __bridge_transfer

实际上是从内存安全性上作的转换修饰符,相关搜索id与void *转换能够自行查阅,并且在iOS的core fundation开发中很是常见,简单的说就是bridgeretained会把内存全部权同时归原对象和变换后的对象持有(只对变换后的对象作一次reatain),bridgetransfer会把内存全部权完全移交变换后的对象持有(retain变换后的对象,release变换前的对象)

这里面我会贴一段代码,这段代码只为展现一些使用,所以,设计上可能有点绕,和扯淡,只为展现混编

--TrickInterface.h
typedef void (*interface)(void* caller, void *parameter);



--TrickOC.h
#import <Foundation/Foundation.h>
#import "TrickInterface.h"

@interface TrickOC : NSObject
{
    int abc;
}

-(int)dosthing:(void*)param;
@property interface call;
@end

--TrickOC.m
#import "TrickOC.h"
#import "TrickInterface.h"

void MyObjectDoSomethingWith(void * obj, void *aParameter)
{
    [(__bridge id) obj dosthing:aParameter];
}

@implementation TrickOC

-(id)init
{
    self = [super init];
    if (self) {
        self.call = MyObjectDoSomethingWith;
    }
    return self;
}

-(int)dosthing:(void *)param
{
    NSLog(@"111111");
    return 0;
}

@end

--TrickCpp.cpp
#include "TrickCpp.h"
#include "TrickInterface.h"

TrickCpp::TrickCpp(void* oc,interface call)
{
    myoc = oc;
    mycall = call;
}

void TrickCpp::function()
{
    mycall(myoc,NULL);
}

--TrickCpp.h
#include <iostream>
#include "TrickInterface.h"
class TrickCpp
{
    void* myoc;
    interface mycall;
public:
    TrickCpp();
    TrickCpp(void* oc,interface call);
    ~TrickCpp();
    void function();
    // constructor, destructor, other members, etc.
};


--使用样例

TrickOC* trickoc = [[TrickOC alloc]init];
    void* pointer = (__bridge void*)trickoc;
    TrickCpp * trick = new TrickCpp(pointer,trickoc.call);
    trick->function();

复制代码

这段代码中首先在全局区域声明了一个全局的cfunctioninterface,起名叫接口顾名思义是打算把它当作C++传递OC的通道,全部跨C++回调OC都经过这个通道来通讯

在TrickOc.m文件中也实现了这一个全局的cfunctionMyObjectDoSomethingWith,这个cfunction实体就是咱们的接口通道

当建立TrickCpp的时候,将以建立好的TrickOc和这个cfunction一并传入,当Cpp须要调用Oc的时候,直接使用cfunction与TrickOc的对象

  • 本着代码上尽可能隔离两种语言避免开发上的混乱和困难,有时候须要一些设计,好比C++作三方库在一个以OC为主的环境中进行使用,OC须要任意调用C++的各类接口和对象,可是不但愿三方库直接引用oc头文件,但愿三方库解耦,只经过固定回调或者协议来通讯
  • 这demo代码仅仅是一种刻意设计,为了展现故意而为的,真正开发的时候须要根据本身状况好好进行设计

参考文献

blog.csdn.net/weiwangchao…

www.philjordan.eu/article/mix…

bbs.9ria.com/thread-2297…