一、介绍
作为一名C++的程序员,当我需要开始为iOS开发程序时,我意识到我必须要弄懂Objective-C中的一些非常怪异的编码风格。这篇文章正是为C++程序员快速适应iOS开发的指南。
但是请注意了,这并不是一篇完整的指南,但是至少可以让你少读100多页的手册。同时,我想你会爱上我的文章风格的!
二、背景
阅读前请确认你是懂C++的,我在这里将会对C++和Objective-C进行比较。此外,由于Objective-C对象跟IUnknown非常相近,这对COM编程也是有用的,所以,了解一点儿COM的知识也是又用的,但不是必须要求掌握。
Objective-C++是C++和Objective-C的混合体,所以你可以将C++的代码混合到Objective-C的代码中,只是记住要把文件名的后缀由.m改为.mm就可以了。
三、Taram-Taram!(原文如此,有知道的朋友可以告诉我这是什么意思)
马上就开始我们的美好旅程!在代码片中,首先我会给出一段Objective-C代码,接着会给出功能相同的C++代码。
成员方法(Member functions)
- ( int ) foo : ( int ) a : ( char ) b {} |
+ ( int ) foo : ( int ) a : ( char ) b {} |
static int foo( int a, char b) {} |
- ( void ) foo2 val1:( int ) a; |
-表示这是一个普通的成员方法(只能通过实例对象才能访问),+表示这是一个静态方法,访问时不需要将累实例化。当然了,就像在C++中一样,静态成员方法是无法访问实例变量的。
另外,Objective-C中的方法命名可以让我们非常清楚的知道每一个传递的参数是干什么的,有什么价值。理论上说,命名参数是可以让程序员以任何顺序传递参数,但是不是这样的。Objective-C在调用方式时,传递参数的顺序需要跟声明时的顺序一样。
请求一个实例成员和一个静态成员(Calling a member through a pointer, or a static member)
[NSObject staticfoo:5:3]; |
CPPObject::staticfoo(5,3); |
Objective-C使用[]来请求成员方法,给方法传递参数时使用:作为分隔符。这点儿不像C++,在Objective-C中,如果ptr是一个空指针(nil),这个请求就会被抛弃掉,这点儿比较友好,但如果在C++中,就会抛出一个pointer violation exception异常。这样我们就可以避免了对空对象的检测了。
协议和接口(Protocols vs Interfaces)
- ( void ) somefunction { ... } |
virtual void somefunction() = 0; |
class c1 : public NSObject, public foo |
void somefunction() { ... } |
协议就相当于是一个抽象类。Objective-C与C++不同的就是,在Objective-C中,你可以将方法设置为可选择(optional)实现的,而不是必须要实现的。你可以将一个方法设置为可选择实现的方法,这只是在告诉编译器这个方法是可选择性实现的,但这编译时并不是必须要这样做的,我们也可以不设置。
检测一个方法是否实现(Checking if a method is implemented)
Objective-C中的成员方法又叫做消息(Smalltalk编程风格)。在Objective-C中,消息接受者(receiver,就是接收这个消息的对象指针)响应一个选择器(selector),这就是说我们首先实现了一个虚拟的方法,并将这个方法作为消息发送给消息接收者,然后消息接收者再在自己的方法库中找,如果有就响应这个方法,没有就抛出异常。然而在C++中,接口中的方法必须全部实现,这跟Objective-C是不同的。
if ([ptr respondsToSelector:@selector(somefunction::)] |
所以现在我们能够确认的是,在Objective-C中,接收者会响应一个选择器,我们可以发送任何消息请求,然后由消息选择器来进行判断。在C++中,这个检测过程是不需要的,因为我们已经全部实现好了,没实现接口的方法会编译报错的。注意了,我们必须要知道选择器可以获取多少个参数,在Objective-C中,@selector最多只能获取两个参数。
类型向下转换(Downcasting)
if ([ptr isKindOfClass:[foo class ]] |
foo* f = dynamic_cast (ptr); |
Objective-C的类型向下转换跟C++比较相同,对于所有Objective-C的类来说,只有通过isKindOfClass方法才能实现类型的向下转换。
协议遵循(Conforms to protocol?)
if ([ptr conformsToProtocol:@protocol(foo)] |
现在我们检测这个消息接收对象是否遵循了某一个协议(在C++中,就是是否实现了某一接口),以便我们可以发送协议的消息内容。这点儿跟Java的类和接口的关系非常的像,而这点儿跟C++也没有多大的不同。
空类型、ID、SEL(void * or id or SEL?)
if ([ptr conformsToProtocol:@protocol(foo)] |
foo* f = dynamic_cast (ptr); |
id就是一个泛类型的void *,可以是Objective-C的任意一个类。你必须使用id替代void *,因为这个才能被ARC管理。SEL是一个泛类型的消息选择器(类似于C++的方法指针)。通常使用@selector带上方法名称和方法参数(:::,多少决定于这个方法需要传递多少参数)来创建消息选择器。选择器也可以是字符串,可以在运行时绑定方法名称和参数传递运行。
类定义,方法和数据、继承(Class declaration, method, data, inheritance)
class f1 : public CPPObject |
在Objective-C中使用@implementation和@end标签来实现类(而在C++中使用::可在任何地方实现类方法)。使用@class keyword来进行使用前提前声明类已经定义了。Objective-C使用private来保护数据成员(方法必须是public的)。Objective-C使用self关键字来替换this关键字表示对象自身,使用super关键字来访问父类。
构造函数和析构函数(Constructors and destructors)
NSObject* s = [NSObject alloc] init]; |
CPPObject* ptr = new CPPObject(); |
NSObject* s = [NSObject alloc] initwitharg:4]; |
CPPOBject* ptr = new CPPOBject(4); |
通过调用静态方法alloc,Objective-C会为对象进行内存分配,这个方法所有对象都有。在Objective-C中,self关键字代表对象自身,如果创建失败时,可以将self设为nil(如果是在C++中,则是抛出异常)。在内存分配完成后,就可以调用对象成员构造方法来创建对象,init方法是默认的方法。
Objective-C跟COM一样使用引用计数方法管理内存,使用retain方法和release方法(相当于IUnknown的AddRef()方法和Release()方法)。当引用计数器为0时,dealloc方法(也就是析构函数)就会被自动调用,接着这个对象就将从内存中移除。
多线程处理(Multithreading)
- ( void ) threadfunc :(NSInteger*) param; |
- ( void ) threadfunc : (NSInteger*) param |
[self performSelectorOnMainThread: @selector(mt)]; |
[self performSelectorInBackground: @selector(thradfunc:) withObject:1 waitUntilDone: false ]; |
Objective-C已经将这个方法写到了NSObject中,这个方法可以在另外一个线程中执行一个选择器方法,或者在主线程中,一直等待请求等等,更多的可以参考NSObject。
内存管理和ARC(Memory and ARC)
@property (weak) NSAnotherObject* f2; |
NSObject* s = [NSObject alloc] init]; |
在这里你需要忘记你认为在C++中的一些好习惯。Objective-C过去使用垃圾回收器来管理内存,就像我,作为一个C++程序员,对这点是非常的痛恨的,这让我不由得想起Java来,让程序变得非常的慢。但是ARC(automatic reference counting自动引用计数)不一样,这个一个编译时的功能,他在编译时就已经告诉了对象何时该被销毁。有了ARC功能,我们不再需要向对象手动发送retain/release消息来管理内存,编译器已经自动帮我们完成了。
为了帮助编译器决定对象保留多长时间,你同样可以使用weak引用来修饰变量。默认情况下,所有的变量的strong的强引用变量(相当于只要这个变量还存在,这个对象就必须存在)。同时你可以设置为弱引用,只要其他强引用丢失,这个对象就会销毁。这个对于从XCode Builder Interface(就像RC编辑器)获取的类成员来说非常有用,当这个类被销毁时,这些成员同时会失去价值。
字符串(Strings)
NSString* s2 = [NSString stringWithUTF8String: "A C String" ]; |
sprintf (buff, "%s hello to %@" , "there" ,s2); |
const char * s3 = [s2 UTF8String]; |
NSString字符串对象在Objective-C中是不可变的。你可以使用静态方法来创建,或者使用@前缀加字符串文字来创建。你还可以使用%@来描述一个字符串并打印出来。
数组(Arrays)
NSArray* a1 = [NSArray alloc] initWithObjects: @ "hello" ,@ "there" ,nil]; |
NSString* first = [a1 objectAtIndex:0]; |
NSArray和NSMutableArray是Objective-C中处理数组的两个类(区别在于NSArray的数组元素必须在创建对象时通过构造函数传递进来,而NSMutableArray能够在对象创建好后管理数组元素)。构造函数有自己固有的参数格式,你必须在所有参数传递完成后,在最后传递一个nil空对象进去。操作数组的方法有sort、search、insert等,不同的是对于NSArray来说返回值是一个新的数组NSArray对象,而NSMutableArray则只是修改已经存在的数组对象。
归档(Categories)
class MyString : public string |
void printmystring() { printf ( "%s" ,c_str()); } |
@interface MyString (NSString) |
@implementation MyString (NSString) |
C++扩展一个已知的类依赖于继承。这让人觉得非常的讨厌,因为所有使用这个扩展类的用户必须要使用另外一个类型(就像上面的代码中,需要使用MyString类来代替String)。而在Objective-C,允许使用分类的方法(Categories)来扩展一个已知的类,这样可以使用同一个类型。这就允许我们在源码中加载不同的扩展头文建.h(就像NSString+MyString.h)来允许使用同一个类型的对象使用不同分类的方法,而不需要将NSString的类型改为MyString。
代码块和Lambdas表达式(Blocks and Lambdas)
-( void )addButtonWithTitle:(NSString*)title block:( void (^)(AlertView*, NSInteger))block; |
[object addButtonWithTitle:@ "hello" block:[^(AlertView* a, NSInteger i){ }]; |
代码块(block)就是Objective-C模仿lambda方法的方法。查看苹果的官方文档可以了解更多。
针对C++开发人员使用Objective-C和ARC的重要提示
我想你已经知道在你的软件的发行版本中有1/5的错误反馈是在你调试的时候没有发现的。难道是用户们不懂程序员?
让我们看看这里发生了什么事情。在s = 0这一行中,将0附值给了变量,但是,无论怎么样,变量在附值前是需要被释放一次的,所以编译器执行了[s release]。如果变量s的值已经是0了,在测试阶段,不会有什么发生,如果s是nil,则完美的执行了一次[s release]。但是,在发行版本中,s也可能是一个dangling指针,所以在初始化为0之前他可能包含了很多值。
在C++中,这并不是问题,因为这里没有ARC。但在Objective-C中,编译器并没有办法知道这个变量是在附值还是在初始化(如果是后面这种情况,则不会发送release消息)。
下面这是正确的方法:
现在编译器就不会尝试[s release]了,因为他知道这是在初始化对象,千万要消息!
将Objective-C对象转换为C++类型的(Casting from Objective-C objects to C++ types)
void * b = (__bridge void *)a; |
void * c = (__bridge_retained void *)a; |
NSObject* d = (__bridge_transfer NSObject*)c; |
这就是我能分析的所有,但是我的建议比较简单。不要将ARC类型的和非ARC类型的混合在一起。如果你确实需要转换许多的Objective-C对象,使用id来代替void *。否则,你会在运行时得到很多内存管理上的问题。
Objective-C有而C++没有的(What Objective-C has and C++ hasn’t)
- 归档(Categories)
- 基于NSObject的操作方法
- 常量YES和NO(等同于true和false)
- 常量NIL和nil(等同于0)
- 方法参数命令
- self(相当于this),但是他能在构造函数中被改变
C++有而Objective-C没有的(What C++ has and Objective-C hasn’t)
- Static objects. Objects in Objective-C cannot be instantiated statically or in the stack. Only pointers.
- Multiple inheritance
- Namespaces
- Templates
- Operator overloading
- STL and algorithms ;
- Methods can be protected or private (in Obj-C, only public)
- const/mutable items
- friend methods
- References
- Anonymous function signatures (without a variable name)
我不懂C++就不做过多的翻译啦!
更多阅读
想要了解更多的从C++到Objective-C的内容,可以点击这里。
结语
原文:http://www.codeproject.com/Articles/770577/From-Cplusplus-to-Objective-C-A-quick-guide-for-pr
翻译:虫儿飞(翻译不好,敬请见谅,在不断学习中)
小站翻译文章,转载请著名文章出处:www.xcoder.cn