美文网首页Kevin的IOS开发专题我爱编程
【IOS开发基础系列 整理】OC与c++混编专题

【IOS开发基础系列 整理】OC与c++混编专题

作者: Kevin_Junbaozi | 来源:发表于2018-03-14 00:00 被阅读50次

    1 objective-c和c++混合编程

    1.1 OC调用C++类的方法

            在 Objective-C++中,可以用C++代码调用方法也可以从Objective-C调用方法。在这两种语言里对象都是指针,可以在任何地方使用。例 如,C++类可以使用Objective-C对象的指针作为数据成员,Objective-C类也可以有C++对象指针做实例变量。下例说明了这一点。

            注意:Xcode需要源文件以".mm"为扩展名,这样才能启动编译器的Objective-C++扩展。

    1.1.1 简单示例

    /* Hello.mm

    * Compilewith: g++ -x objective-c++ -framework Foundation Hello.mm  -o hello

    */

    #import <Foundation/Foundation.h>

    class Hello {

        private: 

            id greeting_text;  // holds an NSString

        public:    

            Hello() {

                greeting_text = @"Hello, world!";

            }

            Hello(const char* initial_greeting_text) {

                greeting_text = [[NSString alloc] initWithUTF8String:initial_greeting_text];

            }

            void say_hello() {

                printf("%s\n", [greeting_text UTF8String]);

            }

    };

    @interface Greeting: NSObject {

        @private

            Hello *hello;

    }

    - (id) init;

    - (void) dealloc;

    - (void) sayGreeting;

    - (void) sayGreeting:(Hello*)greeting;

    @end

    @implementationGreeting

    - (id) init {

        if (self = [super init]) {

            hello = new Hello();

        }

        return self;

    }

    - (void) dealloc {

        delete hello;

        [super dealloc];

    }

    - (void) sayGreeting {

        hello->say_hello();

    }

    - (void) sayGreeting:(Hello*)greeting {

        greeting->say_hello();

    }

    @end

    int main() {

        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        Greeting *greeting = [[Greeting alloc] init];

        [greeting sayGreeting];  // > Hello,  world!

        Hello *hello = new

        Hello("Bonjour, monde!");

        [greeting sayGreeting: hello];// > Bonjour,  monde!

        delete hello;

        [greeting release];

        [pool release];

        return 0;

    }

            正如你可以在OC接口中声明C结构一样,你也可以在OC接口中声明C++类。跟C结构一样,OC接口中定义的C++类是全局范围的,不是OC类的内嵌类(这与标准C(尽管不是C++)提升嵌套结构定义为文件范围是一致的)。

            为了允许你基于语言变种条件化地编写代码,OC++编译器定义了__cplusplus和__OBJC__预处理器常量,分别指定C++和OC。    如前所述,OC++不允许C++类继承自OC对象,也不允许OC类继承自C++对象。

    class Base {/* ... */ };

    @interfaceObjCClass: Base ... @end // ERROR!

    class Derived:public ObjCClass ... // ERROR!

            与 OC不同的是,C++对象是静态类型的,有运行时多态是特殊情况。两种语言的对象模型因此不能直接兼容。更根本的,OC和C++对象在内存中的布局是互不 相容的,也就是说,一般不可能创建一个对象实例从两种语言的角度来看都是有效的。因此,两种类型层次结构不能被混合。

            你可以在OC类内部声明C++类,编译器把这些类当作已声明在全局名称空间来对待。就像下面:

    @interface Foo{

        class Bar { ... } // OK

    }

    @end

    Bar *barPtr;// OK

            OC允许C结构作为实例变量,不管它是否声明在OC声明内部。

    @interface Foo{

        struct CStruct { ... };

        struct CStruct bigIvar; // OK

    } ...

    @end

            Mac OS X 10.4以后,如果你设置fobjc- call-cxx-cdtors编译器标志,你就可以使用包含虚函数和有意义的用户自定义零参数构造函数、析构函数的C++类实例来做为实例变量 (gcc-4.2默认设置编译器标志fobjc-call-cpp-cdtors)。OC成员变量alloc完以后,alloc函数会按声明顺序调用构造器。构造器使用公共无参数恰当的构造函数。OC成员变量dealloc之前,dealloc方法按声明顺序反序调用析构函数。OC没有名称空间得概念,不能在C++名称空间内部声明OC,也不能在OC类里声明名称空间。OC类、协议、分类不能声明在C++ template里,C++ template也不能声明在OC接口、协议、分类的范围内。

            但是,OC类可以做C++ template的参数,C++ template参数也可以做OC消息表达式的接收者或参数(不能通过selector)。

    1.1.2 OC++类的头文件定义

            你可能会想使用等价的Objective-C类型和函数将C++代码封装(wrap)起来。比方说,你有一个名为CppObject的C++类(CppObject.h):

    #include <string>

    class CppObject

    {

          public:

              void ExampleMethod(conststd::string& str);

              // constructor, destructor, other members, etc.

    };

            在Objectiv-C类允许定义C++类的成员变量,所以可以首先尝试定义一个ObjcObject封装类(ObjcObject.h):

    #import 

    #import "CppObject.h"

    @interface ObjcObject : NSObject {

        CppObject wrapped;

    }

    - (void) exampleMethodWithString:(NSString*)str;

    // other wrapped methods and properties

    @end

            然后在ObjcObject.mm中实现这些方法。不过,此时会在两个头文件(ObjcObject.h&CppObject.h)中得到一个预处理和编译错误。问题出在#include和#import上。对于预处理器而言,它只做文本的替换操作。所以#include和#import本质上就是递归地复制和粘贴引用文件的内容。这个例子中,使用#import "ObjcObject.h"等价于插入如下代码:

    // [首先是大量Foundation/Foundation.h中的代码]

    // [无法包含],因为它仅存在于C++模式的include path中

    class CppObject

    {

        public:

              void ExampleMethod(conststd::string& str);

              // constructor, destructor, other members, etc.

    };

    @interface ObjcObject : NSObject {

        CppObject wrapped;

    }

    - (void)exampleMethodWithString:(NSString*)str;

    // other wrapped methods and properties

    @end

            因为class CppObject根本不是有效的Objective-C语法,所以编译器就被搞糊涂了。 错误通常是这样的:

    Unknown type name'class'; did you mean 'Class'?

            正是因为Objective-C中没有class这个关键字.所以要与Objective-C兼容,Objective-C++类的头文件必须仅包含Objective-C代码,绝对没有C++的代码-这主要是影响类型定义(就像例中的CppObject类)。

    1.1.3 简洁头文件——使用ivars

            之前的文章已经提到一些解决方案.其中最好的一个是PIMPL,它也适用于现在的情况。这里还有一个适用于clang的新方法,可以将C++代码从Objective-C中隔开,这就是class extensions中ivars的。

            Class extensions(不要同categories弄混)已经存在一段时间了:它们允许你在class的接口外的扩展部分定义在@implementation段前,而不是在公共头文件中。这个例子就可以声明在ObjcObject.mm中:

    #import "ObjcObject.h"

    @interface ObjcObject() // note the empty parentheses

    - (void)methodWeDontWantInTheHeaderFile;

    @end

    @implementation ObjcObject

    //etc.

            GCC也支持这个操作。不过clang还支持添加ivar块,也就是你还可以声明C++类型的实例变量,既可以在classextension中,也可以在@implementation开始的位置。本例中的ObjcObject.h可以被精简为:

    #import <Foundation/Foundation.h>

    @interface ObjcObject : NSObject

    - (void)exampleMethodWithString:(NSString*)str;

    //other wrapped methods and properties

    @end

            去掉的部分都移到实现文件的class extension中(ObjcObject.mm):

    #import "ObjcObject.h"

    #import "CppObject.h"

    @interface ObjcObject(){

      CppObject wrapped;

    }

    @end

    @implementation ObjcObject

    - (void)exampleMethodWithString:(NSString*)str

    {

    //NOTE: str为nil会建立一个空字串,而不是引用一个指向UTF8String空指针.

        std::string cpp_str([str UTF8String],[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);

        wrapped.ExampleMethod(cpp_str);

    }

            如果我们不需要interface extension来声明额外的属性和方法,ivar块仍然可以放在@implementation开始位置:

    #import "ObjcObject.h"

    #import "CppObject.h"

    @implementation ObjcObject{

        CppObject wrapped;

    }

    - (void)exampleMethodWithString:(NSString*)str

    {

    //NOTE: str为nil会建立一个空字串,而不是引用一个指向UTF8String空指针.

        std::string cpp_str([str UTF8String],[str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);

        wrapped.ExampleMethod(cpp_str);

    }

            定义的CppObject实例wrapped在ObjcObject创建时,CppObject的缺省建构函数会被调用,而在ObjcObject被调用dealloc析构时,ObjcObject的析构函数也会被调用。如果ObjcObject没有提供缺省的建构函数,编译就会失败。

    1.1.4 管理被封装C++对象的生命周期

            解决方案是透过new关键字掌握建构过程,比如:

    @interface ObjcObject(){

        CppObject *wrapped;//指针!会在alloc时初始为NULL.

    }

    @end

    @implementation ObjcObject

    - (id)initWithSize:(int)size

    {

            self = [super init];

            if(self)

            {

                wrapped = new CppObject(size);

                if(!wrapped) self = nil;

            }

            return self;

    }

    //...

            如果是使用C++异常,也可以使用try {...} catch {...}把创建过程封装起来.相应地,还要显式地释放封闭对象:

    - (void)dealloc

    {

        deletewrapped;

        [super dealloc];//如果使用了ARC,这句就要略去

    }

            作者接着提到了另一个方法,显示分配一块内存,然后在它的基础上调用new来创建对象。首先声明char wrapped_mem[sizeof(CppObject)];再使用wrapped = new(wrapped_mem) CppObject();创建了实例wrapped。释放时if (wrapped) wrapped->~CppObject();这样虽然可行,但不建议使用。

    1.1.5 总结

            一定要确保封装的方法仅返回和使用C或Objective-C类型的返回值及参数。同时不要忘记C++中不存在nil,而NUL是不可用于解引用的。

    1.2 在C++代码中使用Objective-C类

    1.2.1 简单示例

            这个问题同样存在于头文件中。你不能因为引入Objective-C类型而污染了C++头文件,或无法被纯C++代码所引用。比方说,我们想封装的Objective-C类ABCWidget,在ABCWidget.h声明为:

    #import <Foundation/Foundation.h>

    @interface ABCWidget

    - (void)init;

    - (void)reticulate;

    //etc.

    @end

            这样的类定义在Objective-C++中是没有问题的,但在纯C++的代码是不允许的:

    #import "ABCWidget.h"

    namespace abc

    {

        class Widget

        {

                ABCWidget *wrapped;

            public:

                Widget();

                ~Widget();

                void Reticulate();

        };

    }

            一个纯粹的C++编译器在Foundation.h中的代码和ABCWidget声明位置出错。

    1.2.2 永恒的PIMPL

            有没有这样的东西作为一类扩展C++,这样的把戏将无法正常工作。 另一方面,PIMPL,工作得很好,实际上是比较常用的纯C++了。在我们的例子中,我们减少到最低限度:C++类

            C++并没有之前提到的class extension,但是却有另一种较为常用的方式:PIMPL(Private Implementation,私有实现)。这里,将C++class的定义精简为:

    namespace abc

    {

        struct WidgetImpl;

        class Widget

        {

            WidgetImpl *impl;

            public:    

                Widget();

                ~Widget();

                void Reticulate();

        };

    }

            然后在Widget.mm中:

    #include "Widget.hpp"

    #import "ABCWidget.h"

    namespace abc

    {

        struct WidgetImpl

        {

            ABCWidget *wrapped;

        };

        Widget::Widget():impl(newWidgetImpl)

        {

            impl->wrapped = [[ABCWidgetalloc] init];

        }

        Widget::~Widget()

        {

            if(impl)

                [impl->wrapped release];

            delete impl;

        }

        void Widget::Reticulate()

        {

            [impl->wrapped reticulate];

        }

    }

            它的工作原理是——前置声明。声明这样的结构或类对象的指针成员变量、结构或类就足够了。需要注意的是封装的对象会在析构函数中释放。即便对于使用了ARC的项目,我还是建议你对这样的C++/Objective-C重引用的文件屏蔽掉它。不要让C++代码依赖于ARC。在XCode中可以针对个别文件屏蔽掉ARC。Target properties->Build phase页签,展开'CompileSources',为特定文件添加编译选项-fno-objc-arc。

    1.2.3 C++中封装Objective-C类的捷径

            您可能已经注意到,PIMPL解决方案使用两个级别的间接引用。如果包装的目标类像本例中的一样简单,就可能会增大了复杂性。虽然Objective-C的类型一般不能使用在纯C++中,不过有一些在C中实际已经定义了。id类型就是其中之一,它的声明在头文件中。虽然会失去一些Objective-C的安全性,你还是可以把你的对象直接传到C++类中:

    #include <objc/objc-runtime.h>

    namespace abc

    {

        class Widget

        {

            id/*ABCWidget* */wrapped;

            public:

                Widget();

                ~Widget();

                void Reticulate();

        };

    }

            不建议向id对象直接发送消息。这样你会失去很多编译器的检查机制,特别是对于不同类中有着相同selector名字的不同方法时。所以:

    #include "Widget.hpp"

    #import "ABCWidget.h"

    namespace abc

    {

        Widget::Widget() : wrapped([[ABCWidgetalloc] init])

        {

        }

        Widget::~Widget()

        {

            [(ABCWidget*)impl release];

        }

        void Widget::Reticulate()

        {

            [(ABCWidget*)impl reticulate];

        }

    }

            像这样的类型转换很容易在代码中隐藏错误,再尝试一个更好的方式。在头文件中:

    #ifdef __OBJC__

    @class ABCWidget;

    #else

    typedef struct objc_object ABCWidget;

    #endif

    namespace abc

    {

        class Widget

        {

            ABCWidget *wrapped;

            public:

                Widget();

                ~Widget();

                void Reticulate();

        };

    }

            如果这个头文件被一个mm文件引用,编译器可以充分识别到正确的类。

            如果是在纯C++模式中引用,ABCWidget*是一个等价的id类型:定义为typedef struct objc_object* id;。

            #ifdef块还可以被进一步放到一个可重用的宏中:

    #ifdef __OBJC__

    #define OBJC_CLASS(name) @class

    name

    #else

    #define OBJC_CLASS(name) typedef

    struct objc_object name

    #endif

            现在,我们可以前置声明在头文件中一行就可以适用于所有4种语言:

    OBJC_CLASS(ABCWidget);

    1.3 C++词汇歧义和冲突

            OC头文件中定义了一些标识符,所有的OC程序必须包含的,这些标识符识id,Class,SEL,IMP和BOOL。

            OC方法内,编译器预声明了标识符self和super,就像C++中的关键字this。跟C++的this不同的是,self和super是上下文相关的;OC方法外他们还可以用于普通标识符。

            协议内方法的参数列表,有5个上下文相关的关键字(oneway,in,out,inout,bycopy)。这些在其他内容中不是关键字。

            从OC程序员的角度来看,C++增加了不少新的关键字。你仍然可以使用C++的关键字做OC selector的一部分,所以影响并不严重,但你不能使用他们命名OC类和实例变量。例如,尽管class是C++的关键字,但是你仍然能够使用NSObject的方法class:

    [foo class];// OK

            然而,因为它是一个关键字,你不能用class做变量名称:

    NSObject *class; // Error

            OC里类名和分类名有单独的命名空间。@interface foo和@interface(foo)能够同时存在在一个源代码中。OC++里,你也能用C++中的类名或结构名来命名你的分类。

            协议和template标识符使用语法相同但目的不同:

    id<someProtocolName> foo;

    TemplateType<SomeTypeName> bar;

            为了避免这种含糊之处,编译器不允许把id做template名称。

            最后,C++有一个语法歧义,当一个label后面跟了一个表达式表示一个全局名称时,就像下面:

    label:::global_name = 3;

            第一个冒号后面需要空格。OC++有类似情况,也需要一个空格:

    receiver selector: ::global_c++_name;

    1.4 限制

            OC++没有为OC类增加C++的功能,也没有为C++类增加OC的功能。例如,你不能用OC语法调用C++对象,也不能为OC对象增加构造函数和析构函数,也不能将this和self互相替换使用。类的体系结构是独立的。C++类不能继承OC类,OC类也不能继承C++类。另外,多语言异常处理是不支持的。也就是说,一个OC抛出的异常不能被C++代码捕获,反过来C++代码抛出的异常不能被OC代码捕获。

    摘自:http://ocen.iteye.com/blog/522028

    2 参考链接

    混合使用Objective-C,C++和Objective-C++

    http://blog.csdn.net/horkychen/article/details/7935910

    objective-c和c++混合编程

    http://www.cnblogs.com/85538649/archive/2011/09/29/2195332.html

    相关文章

      网友评论

        本文标题:【IOS开发基础系列 整理】OC与c++混编专题

        本文链接:https://www.haomeiwen.com/subject/wvdufftx.html