self = [super init]"的解释与潜藏bug

时间:2021-04-24 04:28:13

[Obj-C笔记] "self = [super init]"的解释与潜藏bug

Objective-C的推荐init方法写法如下:

- (id) init
{
if(self = [super init])
{
//为子类增加属性进行初始化
}
return self;
}

这里涉及了几个问题,

1. [super init]的作用:

面向对象的体现,先利用父类的init方法为子类实例的父类部分属性初始化。

2. self 为什么要赋值为[super init]:

简单来说是为了防止父类的初始化方法release掉了self指向的空间并重新alloc了一块空间。这时的话,[super init]可能alloc失败,这时就不再执行if中的语句。

3. super作为消息接受者的实质:

super并不是真正的指针,[super message]的实质是由self来接受父类的message。需要注意的是,[super message]中,message方法出现的self为[super message]语境中的self,即子类实例。

 

潜藏的bug:

假设有父类AObj与子类BObj。

当AObj的init方法如下:

- (id) init
{
id tmp = self;
self = [AObj alloc];
[tmp release];
//other staffs
return self;
}

BObj的init方法如下:

- (id) init
{
if(self = [super init])
{
//other staffs
}
return self;
}

这时编译能通过,但当BObj的实例使用到BObj扩充的属性时,就会出现一个运行时错误。错误的原因在于AObj的init方法用[AObj alloc]重新获得了一块仅仅适合存放AObj实例的空间。而BObj的init方法以为这是块适合存放BObj的空间。当试图读写BObj的扩充属性时便会产生运行时错误。

因此,当init方法需要重新alloc一块空间时,正确的写法如下:

- (id) init
{
id tmp = self;

self = [[self class] alloc];

[tmp release];
//other staffs
return self;
}

注意第5行,[self class]将获得self指向的实例对应的类实例,本例中便是BObj。这样AObj的任何子类的init方法都能保证安全了。





if( self = [super init] )这是一种通常的建议写法,赋值并测零只是为了防止超类在初始化过程中发生改变,返回了不同的对象




为什么一定要    super alloc  ?



众所周知,Objective-C是一门面向对象的语言,一般情况下,我们在Objective-C中定义一个类时,总要提供一个初始化方法,一般大家都是这样写的:

1 -
(MyClass *)init
2 {
3     self = [super init]; 
4     if (self) {
5          //执行一些资源、变量的初始化工作
6     }
7         return self;
8 }

这样一段简单的代码,却有很多可以思考的问题:

1、为什么要通过[super init]来调用父类的初始化方法,父类的初始化方法里又执行了什么东西?

      首先,我们知道对象继承的概念,一个子类从父类继承,那么也要实现父类的所有功能,这就是is-a的关系,比如说狗是哺乳动物,那么狗必定具有哺乳动物的特征和功能。所以在子类的初始化方法中,必须首先调用父类的初始化方法,以实现父类相关资源的初始化。例如我们在初始化狗这一对象时,必须先初始化哺乳动物这一对象,并把结果赋予狗,以使狗满足属于哺乳动物这一特征。

     典型的,在iOS下,所有的类都继承于NSObject,而NSObject的init方法很简单,就是return self。当父类的初始化完成之后,即self不为nil的情况下,就可以开始做子类的初始化了。

2、是否一定要提供初始化方法,是否一定要使用init作为初始化方法?

     我们在Objective-C中创建一个对象通常使用

1 MyClass *newclass = [[MyClass alloc] init];

     或者

 
MyClass *newclass = [Myclass new];  

new方法是NSObject对象的一个静态方法,根据apple的文档,该方法实际上就是alloc和init方法的组合,实际上二者是一样的,但 apple还是推荐我们使用第一种方法,为什么呢?因为使用第一种方法,你可以使用自己定义的init方法来做一些初始化(用自己写的init*****方法),当然,如果子类没有提供 init方法,自然调用的就是父类的init方法了。所以说,从安全性的角度来收,作为开发者我们在对象使用之前是一定要对对象进行初始化的,因此在定义类的时候一定要提供初始化方法。但是否一定要使用init作为方法名呢?答案是不一定。使用init作为方法名只是你重写了NSObject的init方法而已,如果你自己重新定义一个初始化方法,也是完全可以的,只要你在使用的时候记得调用新定义的初始化方法就可以了。

但是,这种方法从设计角度来看我觉得是不可取的。在可复用性方面会比较差,如果确有必要定义一些接受不同参数的初始化方法,我的建议是,先定义一个init的公用方法,再到其他方法中调用它,如:

01 -
(id)init   init的公用方法
02 {
03     self = [super init]; 
04     if (self) {
05            
06     }
07     return self;
08 }
09  
10 -
(id)initWithString:(NSString *)aString
11 {
12     [self init];
13     self.name = aString;  
14 }
15   
16 -
(id)initWithImage:(UIImage *)aImage
17 {
18     [self init];
19     self.image = aImage;  
20 }

补充:

在面向对象编程中,如果编写一个类而没有包含构造函数,这个类仍能编译并且完全可以正常使用。如果类没有提供显式的构造函数,编译器会提供一个默认的构造函数给你。除了创建对象本身,默认构造函数的唯一工作就是调用其超类的构造函数。在很多情况下,这个超类是语言框架的一部分,如java中的 Object类,objective-c 中的NSObject类。

不论是何种情况,在类中至少包含一个构造函数是一种很好的编程实践,如果类中有属性,好的实践往往是初始化这些属性。







self = [super init]一般只用于init方法中,完整的初始化函数一般是这样的:

-(id)init

{

    self=[super init];

    if(self)

    {

        //Your other init codes.

    }

    return self;

}

因为NSObject 都有-(id)init;方法,当你的类直接或者间接继承了NSObject时,在重写该方法时都要[super init] 这也是为什么一般在-(void)viewDidLoad;方法中的第一句是[super viewDidLoad];


补充一点:

  1. 在初始化的时候,失败了会返回nil,所以在你做任何工作的时候,都要首先保证self不是nil。



linjinxing 2009-09-09 08:51

如何理解self = [super init] ????

经常在类的初始化里看到 self = [super init] 这句话, 该如何理解?

self 应该是指向子类的指针,为什么将超类初始化的值赋值给它?

china_xi2009 2009-09-09 08:54
super 应该是父类的指针, 我觉得是继承父类的方法和属性变量吧
我理解的可能也不对,请牛人解答

傻蛋 2009-09-09 09:06
这个问题这么分析. 我们要完成的工作是对子类对象进行初始化.
这个我们可以把这个子类对象的初始化分成两部分: 继承自父类的对象的初始化 + 子对象本身对象的初始化.
所以[super init] 其实是对父类对象进行初始化, 
if( self = [super init] )
{
       //init local vars
}
在括号里面再对子类新增对象(也可以对父类对象)的初始化操作.

self = [super init] 其实就是"subclass is a instance of the superclass"这条面向对象原则的体现.

不知道有没描述清楚

逍遥一狂 2009-09-09 09:09
子类初始化时最好先执行一下父类的初始化方法,有可能那个方法里有东西要初始化

也可以不执行父类的初识化动作,子类自己完全掌控,前提是:你必须很清楚地知道父类的初始化方法里面都做了些什么,这些动作还要不要,要的话就得自己再实现一遍

wangyun 2009-09-09 09:28
很简单,就是这么规定的

ewangke 2009-09-09 23:16
就这个帖子问个很笨的问题:
为什么很多class的init()方法的返回类型都是(id)?

比如employee class,它的init()为什么不返回(ecmployee *)?

angellixf 2009-09-10 12:24
引用 引用第5楼ewangke于2009-09-09 23:16发表的  :
就这个帖子问个很笨的问题:
为什么很多class的init()方法的返回类型都是(id)?

比如employee class,它的init()为什么不返回(ecmployee *)?



id


id和void *并非完全一样。在上面的代码中,id是指向struct objc_object的一个指针,这个意思基本上是说,id是一个指向任何一个继承了Object(或者NSObject)类的对象。需要注意的是id是一个指针,所以你在使用id的时候不需要加星号。比如id foo=nil定义了一个nil指针,这个指针指向NSObject的一个任意子类。而id *foo=nil则定义了一个指针,这个指针指向另一个指针,被指向的这个指针指向NSObject的一个子类。

kfc5151 2009-09-10 15:44
这个问题其实没有什么好讨论的,主要看[super init]返回的值是什么。

显然   self == [ super init],  这么写完全是个人习惯。

我也可以这样写

    [super init];
    return self;

根本就没有区别。

evangel 2009-09-11 11:47
再顶一下,补充

if( self = [super init] )这是一种通常的建议写法,赋值并测零只是为了防止超类在初始化过程中发生改变,返回了不同的对象。
这种状况极少,即使init改变了对象,新对象也不接受新添加的改变。

so
总结
if( self = [super init] )这样写就对了....

ivan_newsoft 2009-09-13 14:20
不错,又加深了理解,呵呵 self = [super init]"的解释与潜藏bug

crzy 2011-01-11 09:13
迷惑了好久,终于明白了谢谢。。。

crzy 2011-01-11 09:20
顶顶顶顶顶顶顶顶顶顶顶顶顶

zzhangchenn 2011-01-11 09:57
呵呵,学习了。

youminbuluo 2011-01-19 14:11
这个嘛
主要是看[super init]能不能正常完成初始化,要是返回值是nil那就说明super都没有完成初始化,子类就没必要初始化了于是返回nil,于是self=nil
一种固定的套路

danyitianshi 2011-01-19 15:01
很好  明白了不少

为爱西行 2011-07-18 11:25
板凳楼说的挺易懂。
初始化分为两步,一是继承自父类对象的初始化,一是子类本身对象的初始化。
因此,需先判断[super inti],再做本身init。

zeng5261435 2011-07-19 17:47
if( self = [super init] )
也有这么写的
if(( self = [super init]) )
其实都差不多。跟这样写一样
self = [super init];
if(self)
就是为了安全。你把这些都删掉一样可以运行啊!
不过苹果公司建议这样写,都是为了安全。

volcan1987 2011-08-10 17:04
引用 引用第13楼youminbuluo于2011-01-19 14:11发表的  :
这个嘛
主要是看[super init]能不能正常完成初始化,要是返回值是nil那就说明super都没有完成初始化,子类就没必要初始化了于是返回nil,于是self=nil
一种固定的套路 self = [super init]"的解释与潜藏bug

感觉这个说法比较准确

chinamsorg 2011-10-11 00:50
学习了,self = [super init]"的解释与潜藏bugself = [super init]"的解释与潜藏bugself = [super init]"的解释与潜藏bugself = [super init]"的解释与潜藏bug

haizilin 2011-10-11 10:48
引用 引用第8楼evangel于2009-09-11 11:47发表的  :
再顶一下,补充

if( self = [super init] )这是一种通常的建议写法,赋值并测零只是为了防止超类在初始化过程中发生改变,返回了不同的对象。
这种状况极少,即使init改变了对象,新对象也不接受新添加的改变。

....... self = [super init]"的解释与潜藏bug




是这么个情况, 进行父类和子类的初始化 ,并判断父类返回的对象是否是同一个

ifeng锋 2011-10-11 11:40
我建议坐沙发的那位赶紧让给板凳self = [super init]"的解释与潜藏bug

m221539 2011-12-23 09:20
[super init]应该很好理解。关键是为什么要赋值给self。
我觉得应该这样理解,是为了内存释放才这样做的,不然init所申请的内存空间无法释放。
linjinxing 2009-09-09 08:51

如何理解self = [super init] ????

经常在类的初始化里看到 self = [super init] 这句话, 该如何理解?

self 应该是指向子类的指针,为什么将超类初始化的值赋值给它?

china_xi2009 2009-09-09 08:54
super 应该是父类的指针, 我觉得是继承父类的方法和属性变量吧
我理解的可能也不对,请牛人解答

傻蛋 2009-09-09 09:06
这个问题这么分析. 我们要完成的工作是对子类对象进行初始化.
这个我们可以把这个子类对象的初始化分成两部分: 继承自父类的对象的初始化 + 子对象本身对象的初始化.
所以[super init] 其实是对父类对象进行初始化, 
if( self = [super init] )
{
       //init local vars
}
在括号里面再对子类新增对象(也可以对父类对象)的初始化操作.

self = [super init] 其实就是"subclass is a instance of the superclass"这条面向对象原则的体现.

不知道有没描述清楚

逍遥一狂 2009-09-09 09:09
子类初始化时最好先执行一下父类的初始化方法,有可能那个方法里有东西要初始化

也可以不执行父类的初识化动作,子类自己完全掌控,前提是:你必须很清楚地知道父类的初始化方法里面都做了些什么,这些动作还要不要,要的话就得自己再实现一遍

wangyun 2009-09-09 09:28
很简单,就是这么规定的

ewangke 2009-09-09 23:16
就这个帖子问个很笨的问题:
为什么很多class的init()方法的返回类型都是(id)?

比如employee class,它的init()为什么不返回(ecmployee *)?

angellixf 2009-09-10 12:24
引用 引用第5楼ewangke于2009-09-09 23:16发表的  :
就这个帖子问个很笨的问题:
为什么很多class的init()方法的返回类型都是(id)?

比如employee class,它的init()为什么不返回(ecmployee *)?



id


id和void *并非完全一样。在上面的代码中,id是指向struct objc_object的一个指针,这个意思基本上是说,id是一个指向任何一个继承了Object(或者NSObject)类的对象。需要注意的是id是一个指针,所以你在使用id的时候不需要加星号。比如id foo=nil定义了一个nil指针,这个指针指向NSObject的一个任意子类。而id *foo=nil则定义了一个指针,这个指针指向另一个指针,被指向的这个指针指向NSObject的一个子类。

kfc5151 2009-09-10 15:44
这个问题其实没有什么好讨论的,主要看[super init]返回的值是什么。

显然   self == [ super init],  这么写完全是个人习惯。

我也可以这样写

    [super init];
    return self;

根本就没有区别。

evangel 2009-09-11 11:47
再顶一下,补充

if( self = [super init] )这是一种通常的建议写法,赋值并测零只是为了防止超类在初始化过程中发生改变,返回了不同的对象。
这种状况极少,即使init改变了对象,新对象也不接受新添加的改变。

so
总结
if( self = [super init] )这样写就对了....

ivan_newsoft 2009-09-13 14:20
不错,又加深了理解,呵呵 self = [super init]"的解释与潜藏bug

crzy 2011-01-11 09:13
迷惑了好久,终于明白了谢谢。。。

crzy 2011-01-11 09:20
顶顶顶顶顶顶顶顶顶顶顶顶顶

zzhangchenn 2011-01-11 09:57
呵呵,学习了。

youminbuluo 2011-01-19 14:11
这个嘛
主要是看[super init]能不能正常完成初始化,要是返回值是nil那就说明super都没有完成初始化,子类就没必要初始化了于是返回nil,于是self=nil
一种固定的套路

danyitianshi 2011-01-19 15:01
很好  明白了不少

为爱西行 2011-07-18 11:25
板凳楼说的挺易懂。
初始化分为两步,一是继承自父类对象的初始化,一是子类本身对象的初始化。
因此,需先判断[super inti],再做本身init。

zeng5261435 2011-07-19 17:47
if( self = [super init] )
也有这么写的
if(( self = [super init]) )
其实都差不多。跟这样写一样
self = [super init];
if(self)
就是为了安全。你把这些都删掉一样可以运行啊!
不过苹果公司建议这样写,都是为了安全。

volcan1987 2011-08-10 17:04
引用 引用第13楼youminbuluo于2011-01-19 14:11发表的  :
这个嘛
主要是看[super init]能不能正常完成初始化,要是返回值是nil那就说明super都没有完成初始化,子类就没必要初始化了于是返回nil,于是self=nil
一种固定的套路 self = [super init]"的解释与潜藏bug

感觉这个说法比较准确

chinamsorg 2011-10-11 00:50
学习了,self = [super init]"的解释与潜藏bugself = [super init]"的解释与潜藏bugself = [super init]"的解释与潜藏bugself = [super init]"的解释与潜藏bug

haizilin 2011-10-11 10:48
引用 引用第8楼evangel于2009-09-11 11:47发表的  :
再顶一下,补充

if( self = [super init] )这是一种通常的建议写法,赋值并测零只是为了防止超类在初始化过程中发生改变,返回了不同的对象。
这种状况极少,即使init改变了对象,新对象也不接受新添加的改变。

....... self = [super init]"的解释与潜藏bug




是这么个情况, 进行父类和子类的初始化 ,并判断父类返回的对象是否是同一个

ifeng锋 2011-10-11 11:40
我建议坐沙发的那位赶紧让给板凳self = [super init]"的解释与潜藏bug

m221539 2011-12-23 09:20
[super init]应该很好理解。关键是为什么要赋值给self。
我觉得应该这样理解,是为了内存释放才这样做的,不然init所申请的内存空间无法释放。