学习Objective-C--第四天
2011-06-13 22:27 Paul Wong 阅读(11375) 评论(17) 编辑 收藏 举报教程详细:
技术:Objective-C 难度:初学者 完成时间:20-35分钟
欢迎来到学习Objective-C系列教程的第四部分,到目前为止,我们在理论,原则以及语言功能。今天,我们会创建一个像前面举的汽车例子那样的简单类。我们的类会详细描述汽车,并允许我们读写其属性。在今天的例子之后,你将能够用Xcode创建自己的类并熟练运用它。
到目前为止,我们已经收到了一些很好的反馈,包括通过邮件,留言,twitter等。我很高兴看到这么多童鞋对这系列教程感兴趣,更高兴的是,看到大家都动起手来实践并提出问题。呵呵,继续努力吧!
准备开始
首先用Xcode创建一个新项目,在Mac OS X 的分隔符下,点击Application,然后点击Command Line Tool,最后,更改下列表框设置Foundation的类型。
保存该项目,我就命名为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),建立并运行你的程序,你会看到像这样的:
属性和合成(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联系我。呵呵,有一个愚蠢的问题就是你什么都没问,这系列教程是允许自由提问的哟。
文件下载:
系列教程导航