类的实例化位导致两个问题:构造函数、析构函数和赋值运算符如何实现,以及如何分配内存。
在 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]; |