代码改变世界

学习Objective-C--第四天

2011-06-13 22:27  Paul Wong  阅读(11372)  评论(17编辑  收藏  举报

教程详细:

      技术:Objective-C    难度:初学者     完成时间:20-35分钟

  欢迎来到学习Objective-C系列教程的第四部分,到目前为止,我们在理论,原则以及语言功能。今天,我们会创建一个像前面举的汽车例子那样的简单类。我们的类会详细描述汽车,并允许我们读写其属性。在今天的例子之后,你将能够用Xcode创建自己的类并熟练运用它。

   到目前为止,我们已经收到了一些很好的反馈,包括通过邮件,留言,twitter等。我很高兴看到这么多童鞋对这系列教程感兴趣,更高兴的是,看到大家都动起手来实践并提出问题。呵呵,继续努力吧!

准备开始

    首先用Xcode创建一个新项目,在Mac OS X 的分隔符下,点击Application,然后点击Command Line Tool,最后,更改下列表框设置Foundation的类型。

4-1

  保存该项目,我就命名为CarApp吧,当这项目窗口出现时,我们需要创建一个新类。点击Command-N(或 File>New File),在Mac OS X下导航到Cocoa并选择Objective-C类。确保子类是继承于NSObject并点击下一步。命名你的类为SimpleCar并确保 .h 文件被创建了,然后就保存。

  我们的类现在已经创建好了,但它是空的。让我们为它填充些代码吧。还记得在Objective-C中,我们把代码分成两部分吗:接口与实现。它让在接口的工作变得有逻辑意义,所以这是我们的初衷。

编写接口

    打开SimpleCar.h文件,当前的状态看起来是这样的:

1. #import <Cocoa/Cocoa.h>   
2.  
3. @interface SimpleCar : NSObject {   
4.  
5. }   
6.  
7. @end 

  首先,我们引入Cocoa.h,这使我们可以获得像NSString,NSMutableString等这样的东东。然后,我们创建类(SimpleCar)作为NSObject的子类。

  现在, 我们需要确定我们的类需要保存什么样的信息了。既然我们像平常一样用汽车,我们需要存储有关汽车的相关信息,例如:

  • make      牌子   
  • model    型号
  • VIN        汽车识别码

  还有更多信息我们可以写进,但现在不会这样。对于其中的每个属性,我们需要用一个合适的数据类型去存储。牌子和型号是字符范畴(像文件,号码和标点符号),所以用string是比较合适的哟。VIN(汽车识别码)仅仅是由数字组成。我们的代码看起来会像这样(省略前面):

1. @interface SimpleCar : NSObject {   
2.     NSString* make;   
3.     NSString* model;   
4.     NSNumber* vin;   
5. }   
6.  
7. @end 

    我们之前也提过了,为了读写类的数据,会用到一个方法。因此,要设置变量,我们需要增加方法。要做这样,我们提出四点:一个设置牌子,一个设置型号,一个设置VIN,以及最后一个方法用来设置牌子和型号(只是为了告诉大家怎样使用多个参数罢了)。

1. @interface SimpleCar : NSObject {   
2.    NSString* make;   
3.    NSString* model;   
4.    NSNumber* vin;   
5. }   
6.  
7. // set methods   
8. - (void) setVin:   (NSNumber*)newVin;   
9. - (void) setMake:  (NSString*)newMake;   
10. - (void) setModel: (NSString*)setModel;   
11.  
12. // convenience method   
13. - (void) setMake: (NSString*)newMake   
14.        andModel: (NSString*)newModel;   
15.  
16. @end  

  我们在大括号@end之间声明方法,在方法之前放置 “-“是要告诉编译器这个方法是实例方法。一个实例方法是依靠实例执行的方法。相反,一个”+”表示这个方法是类方法,这方法的执行并不需要一个单独的对象,这接下来会讲到。

  我们的第一个方法(setVin)返回void,并以一个NSNumber作为参数。我们的第二个方法(setMake)也相类似,也返回void,并以一个NSString作为参数。第三个方法除了方法名都相似。

  我们最后一个方法也是返回void,但需要两个NSString类型的参数:newMake和newModel。这些方法的命名是与大多数Objective-C方法相似的,都是以简单英语命名。所以,当你读到这方法时,能够明显知道其含义。记住方法的名记是重要的,在这种情况下为:”setMake,andModel“--所有参数名都包括在方法名里了。

  有一点值得注意的是,我们使用(void)是因为我们不需要返回任何东西。既然所有要做的只是设置值,并不需要返回任何东西(例如一表示成功的信息):

  下一步,我们会添加用来访问值的方法。虽然我们称这些方法为get 方法和set方法,我们只用”set”和”get”。怎样命名你的方法由你决定,但是用到了”get”会常于的,有助于避免混淆。

  我们的set方法看起来会像这样:

1. // set methods   
2. - (void) setVin:   (NSNumber*)newVin;   
3. - (void) setMake:  (NSString*)newMake;   
4. - (void) setModel: (NSString*)newModel;   
5.  
6. // convenience method   
7. - (void) setMake: (NSString*)newMake   
8.        andModel: (NSString*)newModel;   
9.  
10. // get methods   
11. - (NSString*) make;   
12. - (NSString*) model;   
13. - (NSNumber*) vin; 

  要注意的是, get方法名和类里的变量名相同。这样当我们读书变量时更简单。当你直接访问这变量,基本上对get方法是透明的。

编写实现

  现在,我们的接口已经存在了,并且知道类要干什么了,所以要实现我们的方法了。往回看,我们有四个方法需要实现的:setVin, setMake ,setModel 和setMake:andModel。在移动文件之前,复制这些方法的声明到粘贴板(Cmd+C)。现在关闭SimpleCar.h 并在编辑器里打开 SampleCar.m ,在@implementation和@end之间粘贴下这些方法声明。如下:

1.@implementation SimpleCar   
2.
3.// set methods   
4.- (void) setVin:   (NSNumber*)newVin;   
5.- (void) setMake:  (NSString*)newMake;   
6.- (void) setModel: (NSString*)newModel;   
7. 
8.// convenience method   
9.- (void) setMake: (NSString*)newMake   
10.        andModel: (NSString*)newModel;   
11. 
12.// get methods   
13.- (NSString*) make;   
14.- (NSString*) model;   
15.- (NSNumber*) vin;   
16.  
17.@end 

  显然,这是不对的,我们需要做的是,把花括号的地方和方法的内部工作交换,像这样:

   1:  @implementation SimpleCar   
   2:    
   3:  // set methods   
   4:  - (void) setVin: (NSNumber*)newVin {   
   5:    
   6:  }   
   7:    
   8:  - (void) setMake: (NSString*)newMake {   
   9:    
  10:  }   
  11:    
  12:  - (void) setModel: (NSString*)newModel {   
  13:    
  14:  }   
  15:    
  16:  - (void) setMake: (NSString*)newMake   
  17:          andModel: (NSString*)newModel {   
  18:    
  19:  }   
  20:    
  21:  // get methods   
  22:  - (NSString*) make {   
  23:    
  24:  }   
  25:    
  26:  - (NSString*) model {   
  27:    
  28:  }   
  29:    
  30:  - (NSNumber*) vin {   
  31:    
  32:  }   
  33:    
  34:  @end  

  现在,我们需要为方法添加一些代码了。让我们以getter方法作为开始吧,对于每一个getter方法,我们需要做的就是确保方法返回打算返回的数据。由于这个原因,我们的getter方法像这样:

   1:  - (NSString*) make {   
   2:      return make;   
   3:  }   
   4:    
   5:  - (NSString*) model {   
   6:      return model;   
   7:  }   
   8:    
   9:  - (NSNumber*) vin {   
  10:      return vin;   
  11:  }  

  记住:方法返回的变量是决定于接口文件的,不要把方法名和变量名混淆了哟!

  这很简单吧,我们调用make方法,这make方法返回一个NSString指针--这情况下是make变量。同样对于model和vin(除了vin返回一个数字)。

  现在说说setter方法,首先,我们看看这些代码,然后将越过去,我们的setter方法看上去像这样的:

   1:  // set methods   
   2:  - (void) setVin: (NSNumber*)newVin {   
   3:    
   4:      [vin release];   
   5:      vin = [[NSNumber alloc] init];   
   6:      vin = newVin;   
   7:    
   8:  }   
   9:    
  10:  - (void) setMake: (NSString*)newMake {   
  11:    
  12:      [make release];   
  13:      make = [[NSString alloc] initWithString:newMake];   
  14:    
  15:  }   
  16:    
  17:  - (void) setModel: (NSString*)newModel {   
  18:    
  19:      [model release];   
  20:      model = [[NSString alloc] initWithString:newModel];   
  21:    
  22:  }   
  23:    
  24:  // convenience method   
  25:  - (void) setMake: (NSString*)newMake   
  26:          andModel: (NSString*)newModel {   
  27:    
  28:      // Reuse our methods from earlier   
  29:      [self setMake:newMake];   
  30:      [self setModel:newModel];   
  31:    
  32:  } 

  set方法比get方法有那么一点点棘手。我们想把值传递到每个方法里,为了它们属于这个类。首先,公开这些变量,他们被分配了内存,如果不被分配,则为零,而忽略了对象传递给他们的消息。当我们讨论内存管理的时候,我们将介绍这些问题。

  因为我们在setter方法为我们的对象分配内存,当这对象被从内存里释放时,我们要确保释放它们。做到这一点,我们需要自定义一个释放方法,像这样的:

   1:  -(void) dealloc   
   2:  {   
   3:      [vin release];   
   4:      [make release];   
   5:      [model release];   
   6:      [super dealloc];   
   7:  } 

测试类

     恭喜你,如果你按照上面所说的做,你现在应该有一个可工作的类了。所以,让我们测试它吧。

   打开项目主文件(我的是叫CarApp.m),默认情况下看起来像这样的:

   1:  #import <Foundation/Foundation.h>   
   2:    
   3:  int main (int argc, const char * argv[]) {   
   4:    
   5:      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];   
   6:    
   7:      // Insert custom code here...   
   8:      NSLog(@"Hello, World!");   
   9:    
  10:      [pool drain];   
  11:      return 0;   
  12:  } 

   删除评论和NSLog行,因为我们不再需要它们了。

   为了开始使用我们的类,我们需要把它放时程序里。在下面原始的#import行加上下面这行:

   1:  #import "SimpleCar.h" 

   我们的类现在是可用的了,为了测试它,但我们需要创建一个实例。这里有完整的代码:

   1:  #import <Foundation/Foundation.h>   
   2:  #import "SimpleCar.h"   
   3:    
   4:  int main (int argc, const char * argv[]) {   
   5:    
   6:    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];   
   7:    
   8:      SimpleCar *myCar = [[SimpleCar alloc] init];   
   9:    
  10:      NSNumber *newVin = [NSNumber numberWithInt:123];   
  11:    
  12:      [myCar setVin:newVin];   
  13:      [myCar setMake:@"Honda" andModel:@"Civic"];   
  14:    
  15:      NSLog(@"The car is: %@ %@", [myCar make], [myCar model]);   
  16:      NSLog(@"The vin is: %@", [myCar vin]);   
  17:    
  18:      [myCar release];   
  19:    
  20:    [pool drain];   
  21:    
  22:    return 0;   
  23:  } 

   首先,我们创建一个SimpleCar命叫myCar的实例的指针。接着,我们使用内存分布和初始化--这些会在下面说到的:

   其次,既然我们需要传递一个NSNumber给setVin方法,在这就创建一个呗。再次,我们创建一个命叫newVin的NSNumber实例的指针,并且初始化值为123。 “123”是一个整型,这就是我们使用数字的原因了。

   接下来,我们调用我们的方法,首先我推送myCar该接收的消息,并使用setVin方法。冒号后的值(之前创建的NSNumber)是我们提供给这方法的。接着,我们调用两个参数的setMake方法。这些参数由一个@打头,是为了告诉编译器紧接的是一个string。

   最后,我们释放myCar,因为之前用了它。接下来的会更多的讨论内存管理。

   我们的类在工作了,为了证明这动作,我们加一些NSLog语句来打印一些值到控制台吧。如果你打开控制台(Run>Console),建立并运行你的程序,你会看到像这样的:

4-2

属性和合成(Synthesize)

    看看上面的代码,很多看起来像是乏味的。例如,在我们的getter方法里,只做了返回一个实例变量,但这就要用了三行代码去做如此简单的事。同样,在我们的setter方法里,我们仅设置了实例变量。除了我们方法有两个参数,看起来是重复和臃肿呀。然而,Objective-C为了解决这问题,用了@property和@synthesize,放在我们的访问方法那里,并给出了许多简洁的编码。

   这样,我们新接口文件看起来像使用属性那样了:

   1:  #import "SimpleCar.h"   
   2:    
   3:  @implementation SimpleCar   
   4:    
   5:  @synthesize make, model, vin;   
   6:    
   7:  - (void) setMake: (NSString*)newMake   
   8:          andModel: (NSString*)newModel {   
   9:    
  10:      [self setMake:newMake];   
  11:      [self setModel:newModel];   
  12:    
  13:  }   
  14:    
  15:  @end 

  这看起来是不是更优雅呢?这样想吧,@property替换了所有getter和setter方法的接口声明,而@synthesize则替换它们实际的方法。这样 , getter和setter可以被动态创建了,我们不需要浪费时间去创建它们,除非我们需要一些特别的事情。

  小结:

    到现在,你应该很好的掌握了类,对象和实例了。当然,如果你还没有创建类,那就另当别论了,这东西是需要时间的。通过例子学习是更好的方法,如果你不想动手,那至少也要下载代码去阅读一下,以确保百分百知道这是神马回事。

下一节

   在我们的教程里也提及到了一些内存管理的知识了,这是需要我们掌握的很重要的主题哟,所以我们下次还要研究。诚然,它并不是最有趣的主题,或容易掌握的知识。但它是相当重要的,特别是你想成为熟练的Objective-C开发者。

挑战

  这周的挑战可能会有一点点难度,但我们会看你怎么进行的。首先,如果你重复上面的代码,下载源代码。这挑战就是为项目添加另一个类,但这次,它是SimpleCar的子类(记得,我们是在接口文件定义你类的)。如果你懂得这样做,使用继承方法并尝试添加你自己的代码,例如:engine size , doors或height。

记住: 如果你有任何疑问,留下您的贵言,或在twitter联系我。呵呵,有一个愚蠢的问题就是你什么都没问,这系列教程是允许自由提问的哟。

文件下载:

Inserting of file(CarClassTest.zip) failed. Please try again.

 

系列教程导航

<<学习Objective-C--第三天                         学习Objective-C--第五天>>