Objective-C 2.0 with Cocoa Foundation---对象的初始化以及实例变量的作用域(3)

来源:互联网 发布:路由限速软件 编辑:程序博客网 时间:2024/04/30 11:17
Objective-C 2.0 with Cocoa Foundation---对象的初始化以及实例变量的作用域(3)

  对于objecsive-C里面的类的实例变量而言,在编译器的范围里面,是有作用域的。和其他的语言一样,objecsive-C也支持public,private还有protected作用域限定。

  如果一个实例变量没有任何的作用域限定的话,那么缺省就是protected。

  如果一个实例变量适用于public作用域限定,那么这个实例变量对于这个类的派生类,还有类外的访问都是允许的。

  如果一个实例变量适用于private作用域限定,那么仅仅在这个类里面才可以访问这个变量。

  如果一个实例变量适用于protected作用域限定,那么在这个类里面和这个类的派生类里面可以访问这个变量,在类外的访问是不推荐的。

  我们来看看“Cattle.h”的代码片断:

1     int legsCount;
2     @private
3     bool gender;    //male = YES female = NO
4     @protected
5     int eyesCount;
6     @public
7     NSString *masterName;

  第一行的legsCount的前面没有任何作用域限定,那么它就是protected的。

  第二行是在说从第二行开始的实例变量的定义为private的,和其他的关键字一样,objecsive-C使用@来进行编译导向。

  第三行的gender的作用域限定是private的,所以它适用于private作用域限定。

  第四行是在说从第四行开始的实例变量的定义为protected的,同时第二行的private的声明作废。

  第五行的eyesCount的作用域限定是protected的,所以它适用于protected作用域限定。

  第六行是再说从第六行开始的实例变量的定义为public的,同时第四行的protected的声明作废。

  第七行的masterName的作用域限定是public的,所以它适用于public作用域限定。

  我们再来看看在派生类当中,private,protected还有public的表现。Bull类继承了Cattle类,笔者改写了一下“Bull.m”用来说明作用域的问题,请参看下面的代码:

1 -(void) saySomething
2 {
3     NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount);
4     NSLog(@"I have %d eyes, my master is %@.", eyesCount,masterName);
5     //List below is illegal
6     //NSLog(@"My gender is %@",gender ? @"male" : @"female");
7 }

  我们来看看第3还有第4行代码,我们可以访问legsCount,eyesCount还有masterName。

  在第6行代码当中,我们试图访问gender这个Cattle的私有(private)属性,这行代码产生了编译错误,所以我们不得不注释掉第6行代码。

  好的,我们再来看看类的外部private,protected还有public的表现。请同学们打开“07-InitWithAndIvarScope.m”,参考一下下面的代码:

1     //legal, but not good
2     redBull->masterName = @"that cowgirl";
3     //legal, but bad
4     //redBull->eyesCount = 3;
5    
6     //Trying to access a private ivar, VERY bad thing
7     //MyClass bullClass = redBull->isa;
8     bool *redBullGender = (bool *)(redBull) + 8;
9     NSLog(@"My gender is %@",*redBullGender ? @"male" : @"female");

 在第二行里面,我们访问了masterName,由于在Cattle里面masterName是public的,Bull继承了Cattle,所以我们可以直接访问masterName。但是这不是一种好的习惯,因为这不符合面向对象的基本思想。实际上,如果没有特殊的理由,我们不需要使用public的。

  第四行,我们试图在类的外边访问protected变量eyesCount,在这里笔者的Xcode只是轻轻的给了一个警告,编译成功并且可以运行。同样,这种在类的外边访问类的protected变量是一个很糟糕的做法。

  我们还记得在Bull的saySomething里面我们曾经试图访问过gender,但是编译器无情的阻止了我们,因为gender是私有的。但是,这仅仅是编译器阻止了我们,当我们有足够的理由需要在类的外边访问private实例变量的时候,我们还是可以通过一些强硬的方法合法的访问私有变量的,我们的方法就是使用指针偏移。

  我们首先回忆一下第6章 的6.6节的内容,isa里面保存了对象里面的实例变量相对于对象首地址的偏移量,我们得到了这个偏移量之后就可以根据对象的地址来获得我们所需要的实例变量的地址。在正常情况下,我们需要通过访问类本身和它的超类的ivars来获得偏移量的,但是笔者在这里偷了一个懒,先使用第七行的代码MyClass bullClass = redBull->isa;通过Debugger获得gender的偏移量,数值为8。然后在第8行里面,笔者通过使用指针偏移取得了gender的指针然后在第9行实现了输出。

  由此可见,在objecsive-C里面, 所谓的private还有protected只是一个objecsive-C强烈推荐的一个规则,我们需要按照这个规则来编写代码,但是如果我们违反了这个规则,编译器没有任何方法阻止我们。

  笔者认为在类的外部直接访问任何实例变量,不管这个实例变量是public,private还是protected都是一个糟糕的做法,这样会明显的破坏封装的效果,尽管这样对编译器来说是合法的。

 

  7.4,initWith...

  NSobjecs为我们准备的不带任何参数的init,我们的类里面没有实例变量,或者实例变量可以都是零的时候,我们可以使用NSobjecs为我们准备的缺省的init。当我们的实例变量不能为零,并且这些实例变量的初始值可以在类的初始化的时候就可以确定的话,我们可以重写init,并且在里面为实例变量初始化。

  但是在很多时候, 我们无法预测类的初始化的时候的实例变量的初始值,同时NSobjecs明显无法预测到我们需要什么样的初始值,所以我们需要自己初始化类的实例变量。

  请同学们打开“Cattle.m”,我们参考一下下面的代码:

 1 -(id)init
  2 {
  3     [super init];
  4     return [self initWithLegsCount:4
  5                             gender:YES
  6                          eyesCount:2
  7                         masterName:@ "somebody"];
  8 }
  9 - (id)initWithLegsCount:(int) theLegsCount
10                    gender:(bool) theGender
11                 eyesCount:(int) theEyesCount
12                masterName:(NSString*)theMasterName
13 {
14     legsCount = theLegsCount;
15     gender = theGender;
16     eyesCount = theEyesCount;
17     masterName = theMasterName;
18     return self;
19 }

 

  从第3行到第7行,笔者重写了一下init。在init里面,笔者给通过调用initWith,给类的各个实例变量加上了初始值。这样写是很必要的,因为将来的某个时候,也许有人(或者是自己)很冒失的使用init来初始化对象,然后就尝试使用这个对象,如果我们没有重写init,那么也许会出现一些意想不到的事情。

  从第9行到第19行,是我们自己定义的initWith,代码比较简单,笔者就不在这里赘述了。需要注意的一点是,笔者没有在这里调用[super init];。原因是Cattle的超类就是NSobjecs,初始化的过程就是初始化实例变量的过程,runtime已经为我们初始化好了NSobjecs的唯一实例变量isa,也就是Cattle的类的信息,所以我们不需要调用[super init];。在某些时候,超类的变量需要初始化的时候,请同学们在子类的init或者initWith里面调用[super init];。

  请同学们再次打开“07-InitWithAndIvarScope.m”,参考下面的代码片断:

1     Bull *redBull = [[Bull alloc] initWithLegsCount:4
2                                              gender:YES
3                                           eyesCount:2
4                                          masterName:@"that cowboy"];
5     [redBull setSkinColor:@"red"];
6     [redBull saySomething];

  从第1行到第4行就是调用的initWith来初始化我们的redBull。


原创粉丝点击