从 C&C++过渡到 Objective-C

序:

对于已经熟悉 C++的人来说,从事 iPhone 开发一开始就陷入过多的 Objective-C 的细节可

能并非好事。




我们希望可以更加关注于 iPhone 开发特有的东西,快速上手。




下面这些内容可以帮助 C++程序员快速获得 Objective-C 的基本知识,并开始编写和阅读

iPhone 程序,如果在读别人的程序时,有些语法搞不懂,可以翻查我前面发的那本电子书

《    <Cocoa      入     门     ---   使     用     Objective-C>    英     文    版    》

(http://www.weiphone.com/thread-119791-1-1.html),把其作为参考手册用。




以 下 内 容 均 整 理 / 翻 译 自 《 iPhone                 Open   Application   Development 》

(http://www.weiphone.com/thread-119662-1-1.html)




编/译者: yangqiang.alan@gmail.com




1、消息

你应该注意的第一件事情就是 Objective-C 中大量使用了中括号。在 Objective-C 中,方法

不是使用传统形式(C/C++的语法形式)“调用”的;代替的是,“方法调用”变成了“消息

发送”。与此类似,“方法返回”,变成了“消息响应”。在 C 中,在调用方法之前必须预先

将其定义好;而 Objective-C 的消息风格,允许开发者在运行时动态创建方法和消息。这样
做的负面影响就是:完全允许向一个对象发送一个不可能返回的消息,这将导致异常,进而

可能让程序崩溃。

这里有一个对象 myWidget,一个消息可以被发送到它的 powerOn 方法上,像这样:

returnValue = [ myWidget powerOn ];

如果用 C++做同样的事情,则像这样:

returnValue = myWidget->powerOn();

C 则会在其扁平的名字空间上声明一个函数:

returnValue = widget_powerOn(myWidget);

只要一个对象可以接收,就可以向其传递参数。下面的例子调用了一个 setSpeed 方法,并

传递了两个参数:

returnValue = [ myWidget setSpeed: 10.0 withMass: 33.0 ];

注意,在这个消息中,第二个参数使用了显式名称(explicitly named)。这就允许声明多个具

有相同名称和类型的方法——即多态。

returnValue = [ myWidget setSpeed: 10.0 withMass: 33.0 ];

returnValue = [ myWidget setSpeed: 10.0 withGyroscope: 10.0 ];




2、类与方法声明

尽管可以在 Objective-C 中定义 C++的类,但最好能善用 Objective-C 自己的对象和特性。

Objective-C 中有接口的概念。在标准 C++中,类是结构,而类的变量和方法是被包含在结

构中的。Objective-C 中,把类的变量放在 one part of the class 中,方法放在另一部分中。

Objective-C 要求把 interface 声明在其自己的代码块中(in its own code block),称为

@interface;而其实现则定义在@implementation 代码块中。方法自身的构造跟 Smalltalk
很像,看其来跟 C 非常不同。

我们的组件(widget)的 interface 是下面这样的,放在一个 MyWidget.h 文件中。

#import <Foundation/Foundation.h>




@interface MyWidget : BaseWidget

{

     BOOL isPoweredOn;

     @private float speed;

     @protected float mass;

     @protected float gyroscope;

}

+ (id)alloc;

+ (BOOL)needsBatteries;

- (BOOL)powerOn;

- (void)setSpeed:(float)_speed;

- (void)setSpeed:(float)_speed withMass:(float)_mass;

- (void)setSpeed:(float)_speed withGyroscope:(float)_gyroscope;

@end

在下面的小节中会详细解释这个文件中的重要语义元素。




2.1、Import

使用预处理指令#import 代替传统的#include 指令(尽管#include 还是可以使用的)。使用
#import 的一个好处就是:它内建的逻辑可以保证相同的资源不会被包含多于一次。这就可

以避免像在 C 代码中那样,使用这样的宏:

#ifndef _MYWIDGET_H

#define _MYWIDGET_H

...

#endif




2.2、interface 声明

使用@interface 语句再跟上接口名和基类(如果有基类的话),来声明一个接口。此代码块使

用@end 语句结束。




2.3、方法

方法声明放在花括号结构的外面。+号表示这是一个静态方法,而-号声明这个方法是一个实

例方法。可以使用 MyWidget 类的引用直接调用 alloc 方法(分配一个新对象),而 MyWidget

类的实例方法(比如 needsBatteries 和 powerOn),就通过 alloc 返回的类实例来调用。

方法声明的每个参数,都使用数据类型、局部变量名和一个可选的外部变量名(external

variable name)来表示。外部变量名的例子就是上面程序中的 withMass 和 withGyroscope。

方法调用者在调用方法时,使用外部变量名;但在方法内部,则使用局部变量名来引用这个

参数。这样,setSpeed 方法就使用局部变量_mass 来获取传递进来的 withMass 的值。

如果在声明中没有提供外部变量名,则调用者只会使用一个冒号来引用这个变量,比

如 :10.0。
3、实现

Objective-C 源文件的后缀是.m。前面小节中 widget 类的骨架实现(skeletion implementation)

如下,放在一个 MyWidget.m 文件中。

#import "MyWidget.h"




@implementation MyWidget




+ (id)alloc {

}




+ (BOOL)needsBatteries {

     return YES;

}




- (BOOL)powerOn {

     isPoweredOn = YES;

     return YES;

}




- (void)setSpeed:(float)_speed {

     speed = _speed;

}
- (void)setSpeed:(float)_speed withMass:(float)_mass {

    speed = _speed;

    mass = _mass;

}




- (void)setSpeed:(float)_speed withGyroscope:(float)_gyroscope {

    speed = _speed;

    gyroscope = _gyroscope;

}

@end




就好像接口放在它自己的代码块中一样,实现放在由@implementation 语句开始、@end

语句结束的代码块中。在 C++中,使用 m_作为成员变量名的前缀,是一同常见的做法。而

Objective-C 中,局部变量名使用下划线作为前缀,通过外部变量名,向别人提供可读的变

量名称。




4、Category

Objective-C 向面向对象编程中增加了一个新的元素:称为 category。Category 被设计用来

解决这样的问题:基类被认为是脆弱的,应该避免对其进行看起来无害的改变,因为这样可

能会破坏更加复杂的派生类。当一个程序增长到特定的规模时,开发者经常害怕去触摸那个

较小的基类,因为如果不对整个应用程序详细审查,就无法确定:对基类的改变是否安全。
Category 提供了一个机制,来为基类增加功能,而不影响其他的对象。

category 类可以增加和替换一些基类的方法。这可以在不重新编译基类、甚至不必访问基

类源代码的情况下做到。Category 允许在有限的范围内(within a limited scope)对基类进行

扩展(expanded),那么任何使用基类(而非 category 类)的对象,将继续看到原始的版本。

从开发的角度看,这使得你更容易改进其他开发者所写的类。在运行时,使用 category 的

代码部分将看到这个类的新版本,而直接使用基类的代码则只看到原始版本。

组件工厂发布了一种新型的组件,它可以在太空中飞行,但是如果改变它们的基类,将可能

破坏已经存在的应用。通过构建一个 category,使用 MyWidget 基类的应用程序将继续看见

这个原始类;而新的太空应用将使用 category。下面的例子在已经存在的 MyWidget 基类上

构建了一个新的 category,名为 MySpaceWidget。因为我们需要在太空中爆炸的能力,所

以增加了一个名为 selfDestruct 的方法。这个 category 还用自己的方法替换了已经存在的

powerOn 方法。对比一下:这里使用了圆括号包含 MySpaceWidget,来定义 category;而

在上面的例子中使用的是冒号来定义继承。

#import "MyWidget.h"




@interface MyWidget (MySpaceWidget)

- (void)selfDestruct;

- (BOOL)powerOn;

@end

下面是该 category 的实现:

#import "MySpaceWidget.h"
@implementation MyWidget (MySpaceWidget)




- (void)selfDestruct {

    isPoweredOn = 0;

    speed = 1000.0;

    mass = 0;

}




- (BOOL)powerOn {

    if (speed == 0) {

         isPoweredOn = YES;

         return YES;

    }




    /* Don't power on if the spaceship is moving */

    return NO;

}

@end




5、Pose(伪装)

在 Objective-C 中,子类对象可以伪装成其父类(pose as one of its superclasses),替换父

类对象接收所有的消息。这跟 overriding(重写)很像,但 overriding 只能重写整个类,而非
单个方法。在 Pose 类中,不能声明任何新成员变量,但可以重写或替换已经存在的方法。

Pose 跟 Category 有点相似,它们都允许开发者在运行时扩展一个已经存在的类(augment

an existing class at runtime)。

在前面的例子里,创建了一些机械组件类(mechanical widget classes)。随后,在定义了所

有这些组件之后,发现了永久能源。这就允许很多新的组件成为自治的了,然而老的组件仍

然需要电池。因为自治组件拥有非常庞大的不同代码,所以派生了一个名为

MyAutonomousWidget 的类,来重写所有需要改变的功能(比如静态的 needsBatteries 方

法)。

#import <Foundation/Foundation.h>

#import "MyWidget.h"

@interface MyAutonomousWidget : MyWidget

{




}

+ (BOOL)needsBatteries;

@end




#import "MyAutonomousWidget.h"

@implementation MyAutonomousWidget

+ (BOOL)needsBatteries {

    return NO;

}
@end




自治类不是改变所有已经存在的代码,而是可以简单的伪装成一个组件类。在主程序或其他

高阶(high-level)方法中调用 class_poseAs 方法,从而达到此目的:

  MyAutonomousWidget *myAutoWidget = [ MyAutonomousWidget alloc ];

  MyWidget *myWidget = [ MyWidget alloc ];

  class_poseAs(myAutoWidget, myWidget);

从这里开始,我们放在 pose 类中的任何其他方法(给自治设备用的)都将伪装成原来的基类

被使用。

111

  • 1.
    从 C&C++过渡到 Objective-C 序: 对于已经熟悉C++的人来说,从事 iPhone 开发一开始就陷入过多的 Objective-C 的细节可 能并非好事。 我们希望可以更加关注于 iPhone 开发特有的东西,快速上手。 下面这些内容可以帮助 C++程序员快速获得 Objective-C 的基本知识,并开始编写和阅读 iPhone 程序,如果在读别人的程序时,有些语法搞不懂,可以翻查我前面发的那本电子书 《 <Cocoa 入 门 --- 使 用 Objective-C> 英 文 版 》 (http://www.weiphone.com/thread-119791-1-1.html),把其作为参考手册用。 以 下 内 容 均 整 理 / 翻 译 自 《 iPhone Open Application Development 》 (http://www.weiphone.com/thread-119662-1-1.html) 编/译者: yangqiang.alan@gmail.com 1、消息 你应该注意的第一件事情就是 Objective-C 中大量使用了中括号。在 Objective-C 中,方法 不是使用传统形式(C/C++的语法形式)“调用”的;代替的是,“方法调用”变成了“消息 发送”。与此类似,“方法返回”,变成了“消息响应”。在 C 中,在调用方法之前必须预先 将其定义好;而 Objective-C 的消息风格,允许开发者在运行时动态创建方法和消息。这样
  • 2.
    做的负面影响就是:完全允许向一个对象发送一个不可能返回的消息,这将导致异常,进而 可能让程序崩溃。 这里有一个对象 myWidget,一个消息可以被发送到它的 powerOn方法上,像这样: returnValue = [ myWidget powerOn ]; 如果用 C++做同样的事情,则像这样: returnValue = myWidget->powerOn(); C 则会在其扁平的名字空间上声明一个函数: returnValue = widget_powerOn(myWidget); 只要一个对象可以接收,就可以向其传递参数。下面的例子调用了一个 setSpeed 方法,并 传递了两个参数: returnValue = [ myWidget setSpeed: 10.0 withMass: 33.0 ]; 注意,在这个消息中,第二个参数使用了显式名称(explicitly named)。这就允许声明多个具 有相同名称和类型的方法——即多态。 returnValue = [ myWidget setSpeed: 10.0 withMass: 33.0 ]; returnValue = [ myWidget setSpeed: 10.0 withGyroscope: 10.0 ]; 2、类与方法声明 尽管可以在 Objective-C 中定义 C++的类,但最好能善用 Objective-C 自己的对象和特性。 Objective-C 中有接口的概念。在标准 C++中,类是结构,而类的变量和方法是被包含在结 构中的。Objective-C 中,把类的变量放在 one part of the class 中,方法放在另一部分中。 Objective-C 要求把 interface 声明在其自己的代码块中(in its own code block),称为 @interface;而其实现则定义在@implementation 代码块中。方法自身的构造跟 Smalltalk
  • 3.
    很像,看其来跟 C 非常不同。 我们的组件(widget)的interface 是下面这样的,放在一个 MyWidget.h 文件中。 #import <Foundation/Foundation.h> @interface MyWidget : BaseWidget { BOOL isPoweredOn; @private float speed; @protected float mass; @protected float gyroscope; } + (id)alloc; + (BOOL)needsBatteries; - (BOOL)powerOn; - (void)setSpeed:(float)_speed; - (void)setSpeed:(float)_speed withMass:(float)_mass; - (void)setSpeed:(float)_speed withGyroscope:(float)_gyroscope; @end 在下面的小节中会详细解释这个文件中的重要语义元素。 2.1、Import 使用预处理指令#import 代替传统的#include 指令(尽管#include 还是可以使用的)。使用
  • 4.
    #import 的一个好处就是:它内建的逻辑可以保证相同的资源不会被包含多于一次。这就可 以避免像在 C代码中那样,使用这样的宏: #ifndef _MYWIDGET_H #define _MYWIDGET_H ... #endif 2.2、interface 声明 使用@interface 语句再跟上接口名和基类(如果有基类的话),来声明一个接口。此代码块使 用@end 语句结束。 2.3、方法 方法声明放在花括号结构的外面。+号表示这是一个静态方法,而-号声明这个方法是一个实 例方法。可以使用 MyWidget 类的引用直接调用 alloc 方法(分配一个新对象),而 MyWidget 类的实例方法(比如 needsBatteries 和 powerOn),就通过 alloc 返回的类实例来调用。 方法声明的每个参数,都使用数据类型、局部变量名和一个可选的外部变量名(external variable name)来表示。外部变量名的例子就是上面程序中的 withMass 和 withGyroscope。 方法调用者在调用方法时,使用外部变量名;但在方法内部,则使用局部变量名来引用这个 参数。这样,setSpeed 方法就使用局部变量_mass 来获取传递进来的 withMass 的值。 如果在声明中没有提供外部变量名,则调用者只会使用一个冒号来引用这个变量,比 如 :10.0。
  • 5.
    3、实现 Objective-C 源文件的后缀是.m。前面小节中 widget类的骨架实现(skeletion implementation) 如下,放在一个 MyWidget.m 文件中。 #import "MyWidget.h" @implementation MyWidget + (id)alloc { } + (BOOL)needsBatteries { return YES; } - (BOOL)powerOn { isPoweredOn = YES; return YES; } - (void)setSpeed:(float)_speed { speed = _speed; }
  • 6.
    - (void)setSpeed:(float)_speed withMass:(float)_mass{ speed = _speed; mass = _mass; } - (void)setSpeed:(float)_speed withGyroscope:(float)_gyroscope { speed = _speed; gyroscope = _gyroscope; } @end 就好像接口放在它自己的代码块中一样,实现放在由@implementation 语句开始、@end 语句结束的代码块中。在 C++中,使用 m_作为成员变量名的前缀,是一同常见的做法。而 Objective-C 中,局部变量名使用下划线作为前缀,通过外部变量名,向别人提供可读的变 量名称。 4、Category Objective-C 向面向对象编程中增加了一个新的元素:称为 category。Category 被设计用来 解决这样的问题:基类被认为是脆弱的,应该避免对其进行看起来无害的改变,因为这样可 能会破坏更加复杂的派生类。当一个程序增长到特定的规模时,开发者经常害怕去触摸那个 较小的基类,因为如果不对整个应用程序详细审查,就无法确定:对基类的改变是否安全。
  • 7.
    Category 提供了一个机制,来为基类增加功能,而不影响其他的对象。 category 类可以增加和替换一些基类的方法。这可以在不重新编译基类、甚至不必访问基 类源代码的情况下做到。Category允许在有限的范围内(within a limited scope)对基类进行 扩展(expanded),那么任何使用基类(而非 category 类)的对象,将继续看到原始的版本。 从开发的角度看,这使得你更容易改进其他开发者所写的类。在运行时,使用 category 的 代码部分将看到这个类的新版本,而直接使用基类的代码则只看到原始版本。 组件工厂发布了一种新型的组件,它可以在太空中飞行,但是如果改变它们的基类,将可能 破坏已经存在的应用。通过构建一个 category,使用 MyWidget 基类的应用程序将继续看见 这个原始类;而新的太空应用将使用 category。下面的例子在已经存在的 MyWidget 基类上 构建了一个新的 category,名为 MySpaceWidget。因为我们需要在太空中爆炸的能力,所 以增加了一个名为 selfDestruct 的方法。这个 category 还用自己的方法替换了已经存在的 powerOn 方法。对比一下:这里使用了圆括号包含 MySpaceWidget,来定义 category;而 在上面的例子中使用的是冒号来定义继承。 #import "MyWidget.h" @interface MyWidget (MySpaceWidget) - (void)selfDestruct; - (BOOL)powerOn; @end 下面是该 category 的实现: #import "MySpaceWidget.h"
  • 8.
    @implementation MyWidget (MySpaceWidget) -(void)selfDestruct { isPoweredOn = 0; speed = 1000.0; mass = 0; } - (BOOL)powerOn { if (speed == 0) { isPoweredOn = YES; return YES; } /* Don't power on if the spaceship is moving */ return NO; } @end 5、Pose(伪装) 在 Objective-C 中,子类对象可以伪装成其父类(pose as one of its superclasses),替换父 类对象接收所有的消息。这跟 overriding(重写)很像,但 overriding 只能重写整个类,而非
  • 9.
    单个方法。在 Pose 类中,不能声明任何新成员变量,但可以重写或替换已经存在的方法。 Pose跟 Category 有点相似,它们都允许开发者在运行时扩展一个已经存在的类(augment an existing class at runtime)。 在前面的例子里,创建了一些机械组件类(mechanical widget classes)。随后,在定义了所 有这些组件之后,发现了永久能源。这就允许很多新的组件成为自治的了,然而老的组件仍 然需要电池。因为自治组件拥有非常庞大的不同代码,所以派生了一个名为 MyAutonomousWidget 的类,来重写所有需要改变的功能(比如静态的 needsBatteries 方 法)。 #import <Foundation/Foundation.h> #import "MyWidget.h" @interface MyAutonomousWidget : MyWidget { } + (BOOL)needsBatteries; @end #import "MyAutonomousWidget.h" @implementation MyAutonomousWidget + (BOOL)needsBatteries { return NO; }
  • 10.
    @end 自治类不是改变所有已经存在的代码,而是可以简单的伪装成一个组件类。在主程序或其他 高阶(high-level)方法中调用 class_poseAs 方法,从而达到此目的: MyAutonomousWidget *myAutoWidget = [ MyAutonomousWidget alloc ]; MyWidget *myWidget = [ MyWidget alloc ]; class_poseAs(myAutoWidget, myWidget); 从这里开始,我们放在 pose 类中的任何其他方法(给自治设备用的)都将伪装成原来的基类 被使用。