Objective-C中对象的创建和初始化
(Allocating and Initializing Objects)
(主要内容来自于Apple的电子书《The Objective-C Programming Language》的“Allocating and Initializing Objects”。电子书可以从iBooks商店下载。iOS Developer Library也有同名的文档,但是内容上是不同的。)
在Objective-C中创建对象分为两步:
1 开辟一个对象所需的内存,Allocating object
2 为对象的各个成员赋予初始值,Initializing object
开辟内存这个步骤很简单。一般来说,Objective-C中的所有类都继承于NSObject。NSObject定义了两个方法——alloc和allocWithZone:。子类不用重载和修改这两个方法。直接使用就能开辟创建一个自己子类对象所需的内存。
重要的工作在第二步。
初始化对象的方法一般以init开头,比如无参数的init方法,或者initWithFrame:方法。NSObject类定义了init方法。一般而言,子类需要根据自己需要,定义自己的初始化方法。
初始化方法返回的对象(返回值)
需要注意的是,初始化方法(init...)的返回值不一定和该方法的接收者(或者以C语言角度来说是调用者)同类型的对象。如果初始化失败,返回的对象则是nil。比如依靠一个文件来初始化对象,如果打开文件失败,那么返回的对象是nil。
下面的代码是危险的:
id aObject = [SomeClass alloc];
[aObject init]; //在这里返回值不一定就是aObject,
[aObject someOtherMessage]; //实际应该someOtherMessage的接收者应该是init的返回值,而不是aObject
正确的写法如下:
id aObject = [SomeClass alloc] init];
[aObject someOtherMessage];
实现初始化方法时需要注意的地方
~方法的名字应该以init开头
~方法的返回值类型应该是id
~在实现中需要调用designated initializer(什么是designated initiailizer后面会提到)。一个类有很多初始化方法,其中一个(或者一些)叫designated initializer(翻译成?“指定的初始化方法”?)。实现子类的designated initializer时,必须调用父类的designated initializer。实现子类的其他初始化方法时,需要直接或者间接地调用一下自己的designated initializer。
NSObject的designated initializer方法是init。
~在初始化方法里设置成员变量时不要使用accessor method(访问方法)。
~把初始化方法的返回值赋给self。
~返回self,或者如果初始化失败,返回nil。
初始化失败时的处理
如果失败返回nil, 另外如有必要对self调用release方法。
- (id) init {
self = [super init]; //给self赋值; 调用super的designated initializer
if (self) { //只有super的init返回的值不是nil才做下面的事情
theDate = [ [NSDate alloc] init];
}
return self; //如果失败返回,self的值是nil,返回的nil
}
另一个例子:
- (id) iniWithImage:(NSImage *) aImage {
if (aImage == nil) {
[self release]; //如果传入的参数有问题,那么调用self的release方法
return nil;
}
self = [super init_xxx: yyy];
if (self) {
image = [aImage retian];
}
return self;
}
子类需要确保所有从父类继承而来的初始化方法都能正确工作。
比如类A定义了init方法;子类B定义了initWithName方法。类B要保证init消息也能正确地初始化类B。一个简单的方法是类B的init调用类B的initWithName方法来实现自己:
- init {
return [self initWithName:"default"];
}
The Designated Initializer
一个类的designated initializer确保所有继承而来的成员都被初始化(通过调用super的方法),同时也做了其他的大部分工作。这个类的其他初始化方法调用这个designated initializer。
一个类的designated initializer方法必须调用父类的designated initializer,而不是父类的其他初始化方法。如果不这么做可能会引起死循环。
(具体例子见电子书)
合并开辟内存和初始化内存这两个步骤(Combine allocation and initialization)
有些类提供了一些方法,在这些方法里实现了内存开辟和内存初始化这两个步骤。这些方法被称为convenience constructors(便利的构造函数)。一般形式是+className...。
比如NSArray提供了:
+ (id) array;
+ (id) arrayWithObjects:(id) firstObj, ...;
有时候这些方法很有用。比如,+ (id) arrayWithObjects:(id) firstObj, ...,只有知道了参数的个数才能知道应该分配多大的内存。所以不能将开辟内存和初始化内存这两个步骤分开。
再看下面的例子(singleton),如果对象已有就直接返回已有对象,没必要开辟内存:
+ (Soloist *) soloist {
static Soloist *instance = nil; //note: static
if (instance == nil) {
instance = [self alloc] init];
}
return instance;
}
(返回类型是Soloist *,不是id,在这种情况下是合适的)