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

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


1、关于allocinit嵌套调用:

// 我们总是以如下方式嵌套allocinit调用,为什么?

CExample *example1 = [[CExample alloc] init];


// 而不是像下面这样调用

CExample *example2 = [CExample alloc];

[example2 init];


答案:

因为初始化(init)方法返回的对象可能与分配(alloc)的对象不同。比如类簇中的NSStringNSArray等,实际上是一群隐藏在通用接口之下的与实现相关的类,创建NSString对象时,实际获得的可能是NSLiteralStringNSCFStringNSSimpleCStringNSBallOfString或者其他未写入文档的与实现相关的对象。由于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”


// 下面这么做虽然拆分了allocinit,但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)有以下几种情况:

1superRet==nil

此时父类初始化失败,self随之被赋值为nil并返回,表现正常。

2superRet==self

大部分类的初始化都是这个结果。此时赋值没有任何影响。

3superRet!=self

这种情况正是大部分人疑惑的地方。执行self = [super init]之后,我们创建的对象将会被重定向到另外一块内存上。接下来重点解释这种情况。


回想一下,self是每个方法的一个隐藏参数,指向当前对象。因此,这是一个局部变量。那么,为什么我们要改变一个局部变量的值呢?事实上,self必须要改变。我们将在下面解释为什么要这样做。

[super init]实际上有可能返回不同于当前对象的另外一个对象。

实例变量所在的内存位置到隐藏的self参数之间的距离是固定的,如果从init方法返回一个新对象,则需要更新self,以便其后的任何实例变量的引用可以被映射到正确的内存位置,这也是我们需要使用self = [super init]这种形式的重要原因。


单例模式就是self必须要改变的一种情况。

而且有一个 API可以用一个对象替换新分配的对象。Core DataApple提供的 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]将使所有子类指向这个单件的内存。

这不仅使所有子类互相修改数据,甚至访问子类自己增加的变量的时候,可能会崩溃。

建议不要继承单件、或者保证单件的子类也是单件。


2ClassClusters(类簇),初始化方法返回了不同的子类。

以下是对象初始化后,返回不同的子类的例子:

NSString *str1 = [NSString alloc];

NSString *str2 = [str1 initWithString:@"hello"];

上面的str1str2是不一样的对象,存在于不同的内存块中,在GNUStep里面,str1GSPlaceholderString对象,str2GSCInlineString对象。


3)共享。

先看例子:

NSNumber *n1 = [[NSNumber alloc] initWithInt:1];

NSNumber *n2 = [[NSNumberalloc] initWithInt:1];

以上的n1n2,指向了同一块内存!

由于NSNumber是创建之后就不能修改的对象,所以Foundation在这里做了一些优化,相同数值的NSNumber对象将共享同一块内存。


4)父类可能在初始化中释放了当前的对象并创建了新的内存区域。

这时,子类需要将self指向新的内存区域才能正常工作。所以一定要执行self = [super init];


总结:

在初始化方法中使用self = [super init]语句是Objective-C的标准做法。

一般情况下都要用以上语句来防止父类改变对象的内存地址导致self指针指向无效内存。

但是在父类是单件、类簇或者有共享资源的时候,必须依照实际情况考虑是否加上这行代码。

总之,当需要继承父类的时候,调用父类的init之前,必须知道父类的init方法的工作方式。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值