从 C++ 到 Objective-C 之实例化

类的实例化位导致两个问题:构造函数、析构函数和赋值运算符如何实现,以及如何分配内存。

在 C++ 中,变量默认是“自动的”:除非被声明为 static,否则变量仅在自己的定义块中有意义。动态分配的内存可以一直使用,直到调用了 free() 或者 delete。C++ 中,所有对象都遵循这一规则。

然而在 Objective-C 中,所有对象都是动态分配的。其实这也是符合逻辑的,因为 C++ 更加 static,而 Objective-C 则更加动态。除非能够在运行时动态分配内存,否则 Objective-C 实现不了这么多动态的特性。

分配 allocation 和初始化 initialization 的区别

在 C++ 中,内存分配和对象初始化都是在构造函数中完成的。在 Objective-C 中,这是两个不同的函数。

内存分配由类方法 alloc 完成,此时将初始化所有的实例数据。实例数据将被初始化为 0,除了一个名为 isa 的 NSObject 的指针。这个指针将在运行时指向对象的实际类型。实例数据根据传入的参数初始化为某一特定的值,这一过程将在一个实例方法 instance method 中完成。这个方法通常命名为 init。因此,构造过程被明确地分为两步:内存分配和初始化。alloc 消息被发送给类,而 init 消息则被发送给由 alloc 创建出来的新的对象。初始化过程不是可选的,alloc 之后应该跟着 init,之后,父类的 init 也会被调用,直到 NSObject 的 init 方法。这一方法完成了很多重要的工作。

在 C++ 中,构造函数的名字是规定好的,必须与类名一致。在 Objective-C 中,初始化方法与普通方法没有什么区别。你可以用任何名字,只不过通常都是选用 init 这个名字。然而,我们还是强烈建议,初始化方法名字一定要用 init 或者 init 开头的字符串。

使用 alloc 和 init

调用 alloc 之后将返回一个新的对象,并且应该给这个对象发送一个 init 消息。init 调用之后也会返回一个对象。通常,这就是初始化完成的对象。有时候,如果使用单例模式,init 可能会返回另外的对象(单例模式要求始终返回同一对象)。因此,init 的返回值不应该被忽略。通常,alloc 和 init 都会在一行上。

C++

//Language:  C++
Foo* foo = new Foo;

Objective-C

Language: Objective-C
Foo* foo1 = [Foo alloc];
[foo1 init]; // 这是不好的行为:应该使用 init 的返回值
Foo* foo2 = [Foo alloc];
foo2 = [foo2 init]; // 正确,不过看上去很啰嗦
Foo* foo3 = [[Foo alloc] init]; // 正确,这才是通常的做法

为检查内存分配是否成功,C++ 可以判断 new 返回的指针是否是 0(如果使用的是 new(nothrow) 运算符)。在 Objective-C 中,检查返回值是否是 nil 就已经足够了。

初始化方法的正确示例代码

一个正确的初始化方法应该有如下特点:

  • 名字以 init 开始;
  • 返回能够使用的对象;
  • 调用父类的 init 方法,直到 NSObject 的 init 方法被调用;
  • 保存 [super init...] 的返回值;
  • 处理构造期间出现的任何错误,无论是自己的还是父类的。

下面是一些代码:

C++

 //Language:  C++
    class Point2D
    {
    public:
        Point2D(int x, int y);
    private:
        int x;
        int y;
    };
    Point2D::Point2D(int anX, int anY) {x = anX; y = anY;}
    ...
    
    Point2D  p1(3,4);
    Point2D* p2 = new Point2D(5, 6);

Objective-C

Language: Objective-C
@interface Point2D : NSObject
{
    int x;
    int y;
}
 
// 注意,在 Objective-C 中,id 类似于 void*
// (id) 就是对象的“一般”类型
-(id) initWithX:(int)anX andY:(int)anY;
@end
 
@implementation Point2D
 
-(id) initWithX:(int)anX andY:(int)anY
{
    // 调用父类的初始化方法
    if (!(self = [super init])) // 如果父类是 NSObject,必须进行 init 操作
        return nil; // 如果父类 init 失败,返回 nil
    // 父类调用成功,进行自己的初始化操作
    self->x = anX;
    self->y = anY;
    return self; // 返回指向自己的指针
}
@end
 
...
Point2D* p1 = [[Point2D alloc] initWithX:3 andY:4];