您的位置:首页 > 移动开发 > IOS开发

iOS学习笔记01—初始化对象

2013-03-26 19:10 447 查看
iOS学习笔记01—初始化对象

1、关于alloc和init嵌套调用:

// 我们总是以如下方式嵌套alloc和init调用,为什么?
CExample *example1 = [[CExample alloc] init];

// 而不是像下面这样调用
CExample *example2 = [CExample alloc];
[example2 init];

答案:
因为初始化(init)方法返回的对象可能与分配(alloc)的对象不同。比如类簇中的NSString和NSArray等,实际上是一群隐藏在通用接口之下的与实现相关的类,创建NSString对象时,实际获得的可能是NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString或者其他未写入文档的与实现相关的对象。由于init方法可以接受参数,所以该方法的代码能够检查其接受的参数的值,并判断返回另一个类的对象可能更合适。基于这种一认知,字符串初始化函数可能决定创建一个属于其他类的对象(该对象更适合被期望的字符串的要求),然后返回该对象而不是原来的对象。

还是举个例子来看吧:
有如下3个类,声明如下
@interface Car : NSObject
- (id)initWithCarBrandName: (NSString *)carBrandName;
- (void)print;

@end

@interface Benz : Car

@end

@interface Bmw : Car

@end

实现如下

@implementation Car

- (id)initWithCarBrandName:(NSString *)carBrandName{

[self autorelease];

if ([carBrandName isEqualToString:
@"BMW"]) {
return [[Bmw alloc]init];
}

if ([carBrandName isEqualToString:
@"BENZ"]) {
return [[Benz alloc] init];
}

returnnil;
}

- (void)print{

NSLog(@"I am in Car print");
}

@end

@implementation Benz
- (void)print{

NSLog(@"I am in Benz print");
}

@end

@implementation Bmw
- (void)print{

NSLog(@"I am in Bmw print");
}

@end

调用如下
Car *car1 = [[Car alloc] initWithCarBrandName:@"BMW"];

[car1 print]; //输出“I am in Bmw print”

Car *car2 = [Car alloc];

[car2 initWithCarBrandName:@"BENZ"]; //
实际上-initXXX的结果必须要有assignment,否则单独call -initXXX,等于白做。

[car2 print]; //输出“I am in Car print”

// 下面这么做虽然拆分了alloc和init,但init返回的对象赋给了alloc返回的对象,也可以
Car *car3 = [Car alloc];

car3 = [car3initWithCarBrandName:
@"BENZ"];

[car3 print]; //输出“I am in Benz print”

2、关于self = [super init];
先看一下官方文档给出的初始化示例代码:
-(id)init{

//
为什么要将父类初始化之后,用其返回的对象指针覆盖当前对象的指针。
if (self = [super init]) {

// Do additional init
}

return (self);
}

@end

为什么要将父类初始化之后,用其返回的对象指针覆盖当前对象的指针?
答案:
[super init]的返回值(假设是变量superRet)有以下几种情况:
1)superRet==nil
此时父类初始化失败,self随之被赋值为nil并返回,表现正常。
2)superRet==self
大部分类的初始化都是这个结果。此时赋值没有任何影响。
3)superRet!=self
这种情况正是大部分人疑惑的地方。执行self = [super
init]之后,我们创建的对象将会被重定向到另外一块内存上。接下来重点解释这种情况。

回想一下,self是每个方法的一个隐藏参数,指向当前对象。因此,这是一个局部变量。那么,为什么我们要改变一个局部变量的值呢?事实上,self必须要改变。我们将在下面解释为什么要这样做。
[super init]实际上有可能返回不同于当前对象的另外一个对象。
实例变量所在的内存位置到隐藏的self参数之间的距离是固定的,如果从init方法返回一个新对象,则需要更新self,以便其后的任何实例变量的引用可以被映射到正确的内存位置,这也是我们需要使用self =
[super init]这种形式的重要原因。

单例模式就是self必须要改变的一种情况。
而且有一个 API可以用一个对象替换新分配的对象。Core Data(Apple提供的
Cocoa里面的一个 API)就是用了这种 API,对实例数据做一些特殊的操作,从而让这些数据能够和数据库的字段关联起来。当继承 NSManagedObject类的时候,就需要仔细对待这种替换。在这种情形下,self就要指向两个对象:一个是
alloc返回的对象,一个是 [super init]返回的对象。修改self的值对代码有一定的影响:每次访问实例数据的时候都是隐式的。正如下面的代码所示:

@interface B : A {
int i;
}

@end

@implementation B
-(id) init {

//
此时,self 指向 alloc返回的值

//
假设 A进行了替换操作,返回一个不同的
self
id newSelf = [super init];
NSLog(@"%d", i);//输出
self->i的值
self = newSelf;
//有人会认为 i没有变化

NSLog(@"%d", i);//事实上,此时的 self->i,
实际是 newSelf->i,

//
和之前的值可能不一样了

returnself;
}

@end

...
B* b = [[B alloc] init];

self = [super
init] 简洁明了,也不必担心以后会引入 bug。然而,我们应该注意旧的self指向的对象的命运:它必须被释放。第一规则很简单:谁替换self指针,谁就要负责处理旧的self指针。在这里,也就是
[super init]负责完成这一操作。

总结一下,出现父类指针跟子类指针不一样的情况其实就是父类的init方法返回的对象跟原先创建的对象不一样,分为以下几种:
1)单件。
此时如果执行self = [super
init]将使所有子类指向这个单件的内存。
这不仅使所有子类互相修改数据,甚至访问子类自己增加的变量的时候,可能会崩溃。
建议不要继承单件、或者保证单件的子类也是单件。

2)ClassClusters(类簇),初始化方法返回了不同的子类。
以下是对象初始化后,返回不同的子类的例子:
NSString *str1 = [NSString alloc];
NSString *str2 = [str1 initWithString:@"hello"];
上面的str1和str2是不一样的对象,存在于不同的内存块中,在GNUStep里面,str1是GSPlaceholderString对象,str2是GSCInlineString对象。

3)共享。
先看例子:
NSNumber *n1 = [[NSNumber alloc] initWithInt:1];

NSNumber *n2 = [[NSNumberalloc]
initWithInt:1];
以上的n1和n2,指向了同一块内存!
由于NSNumber是创建之后就不能修改的对象,所以Foundation在这里做了一些优化,相同数值的NSNumber对象将共享同一块内存。

4)父类可能在初始化中释放了当前的对象并创建了新的内存区域。
这时,子类需要将self指向新的内存区域才能正常工作。所以一定要执行self
= [super init];

总结:
在初始化方法中使用self = [super init]语句是Objective-C的标准做法。
一般情况下都要用以上语句来防止父类改变对象的内存地址导致self指针指向无效内存。
但是在父类是单件、类簇或者有共享资源的时候,必须依照实际情况考虑是否加上这行代码。
总之,当需要继承父类的时候,调用父类的init之前,必须知道父类的init方法的工作方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: