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

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

    原文作者:Kevin_Junbaozi
    原文地址: https://www.jianshu.com/p/e4575ec05531
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞