主页 > C卫生活 >CS193P第三堂课摘要及心得笔记

CS193P第三堂课摘要及心得笔记

让大家久等了,Stanford 大学的 iPhone 开发课程笔记第三堂课来了!或许大家有注意到,在第三堂课的课堂上,老师有提到今年的 iTunes U 影片会较去年慢一点释出,所以一直拖到现在才恢复连载,还请大家见谅!

这次的课程内容包含如何建立自订的类别、以及一个类别的生命週期以及记忆体管理,和 Objetive-C 中特别的 Property 概念。

建立自订类别
CS193P第三堂课摘要及心得笔记

在 Objetive-C 中,如果要建立自订的类别,就跟 C++中的模式很类似,需要一个.h 标头档先宣告类别的内容,然后搭配.m 档来实做这些方法的功能。

而诚如第一次笔记中所谈的,所有的 Objective-C 物件都继承成至 NSObject 这个根物件。

#import @interface Person : NSObject { // 宣告类别所拥有的变数 NSString *name; int age; } // 宣告类别所拥有的方法 - name; - setName:value; - age; - setAge:age; - canLegallyVote; - castBallot; @end

以上的这段程式码就是一个简单类别的定义,在@interface 下的括号中包含了这个类别的状态,也就是他所拥有的变数,而在后面的部份则是方法的定义。

还记得我们曾经在之前的课程笔记中提到,在 Objetive-C 中,由减号开头的方法就是实体方法,必须由类别产生出实体之后才能使用。而由加号开头的方法则是类别方法,不需产生出实体就可以使用。

顺便提醒,最后可别忘记在宣告完类别后加入@end 的标籤。

在物件实做的过程,也就是在.m 中实做方法的时候,我们可能会需要传递讯息给自己本身的物件,也就是呼叫自己本身带有的方法,此外,也有可能需要传递讯息给父类别、呼叫父类别的方法。而在 Objetive-C 採用了类似 C++和 Java 的模式,用 self 代替自己、用 super 替代父类别。也就是:

- doSomething { // 呼叫父类别的方法 [super doSomething]; // 呼叫同类别中的方法 [self doAnotherThing]; }物件的生命週期

当我们设计好类别之后,随之便是要在程式中将这些类别拿来使用,也就是产生实体。而在 Objective-C 中,我们通常会透过 [[anyClass alloc] init]; 这样的方式来产生新的物件。

+ alloc 这个类别方法就像是 C 语言中的 malloc 或是 C++中的 new 一样,会在记忆体中产生一个新的物件出来,并回传该记忆体的位置给变数。

而在我们产生完新的物件之后,我们需要呼叫建构子来帮助我们初始化该物件的内容,也就是- init 这个实体方法以及其他以 initWith…开头的实体方法。

也因为上述的原因,我们需要在自己的类别中定义 init 以及 initWith…的相关方法。

- init { // 先让父类别进行初始化 if{ // 再进行其它初始化的工作 age = 0; name = @“Bob”; } return self; }

上面这种写法是 Cocoa 中很常见的写法,先呼叫父类别的初始化方法,在进行自己所需要初始化的动作。

而我们也需要建立一系列不同的初始化方法,来帮助我们用不同的参数初始化物件,向下面这些就是很好的例子:

- init; - initWithName:name; - initWithName:name age:age;

在我们有多重的初始化方法之后,我们实做的时候要记得一个关键:只要写最複杂的那个方法就好,其他的都呼叫那个方法就对了。像是以下就是个例子:

- init { return [self initWithName:@“No Name”]; }- initWithName:name { return [self initWithName:name age:0]; }

除了建立物件之外,学习如何删除物件也是很重要的,在 Cocoa Touch 的环境下,我们并没有像 Java 一样方便的 Garbage Collection 物件自动回收机制可以使用,而必须像 C++一样,自己去追蹤物件的使用,在适当的时候把物件删除。

在 Objetive-C 里面,当物件要从记忆体中删除的时候,会呼叫-dealloc 这个方法。然而我们并不需要自己去呼叫这个方法,因为 Objetive-C 为了方便大家能够持续追蹤物件的使用状况,提供了 reference count 的机制,也就是物件被参考的次数,会被储存在一个变数中。

而我们可以透过 release 这个方法来减少 count、用 retain 方法来增加 count。当 count 的值降到零的时候,物件就会从记忆体中被释放出来。

每当物件被 alloc + init 建立的同时,他的 reference count 就会变成 1,随着物件的操作或传递,我们在适时的进行 release 和 retain 的呼叫,这样当物件的 conut 归零的同时,物件就会被呼叫 dealloc 方法、进而从记忆体中删除。

Memory count

在上面我们提到,我们曾经提到在 Objetive-C 里面必须自己去处理物件在记忆体的建立以及删除,而为了方便我们解决这个问体,NSObject 提供了 memory count 的机制:当物件产生时这个数字会是 1,透过 retain 可以再增加 1,而 release 会减一,当数字小于等于 0 的时候就会呼叫该物件的 dealloc 方法,将物件从记忆体中删除。

此外,我们可以透过呼叫物件的 retainCount 方法来检查目前物件的 memory count。不过需要注意像是下面这种状况:

Person *person = [[Person alloc] init]; // 建立物件 [person release]; // 物件从记忆体中被移除[person doSomething]; // 程式错误!

因为 person 这个变数仍然存着一个记忆体的位置,系统无从得知 person 所指向的记忆体位置是不是已经被释放了,所以必须在第三行加入:

person = nil;

诚如在第一次课堂笔记中所说的,Objetive-C 可以接受呼叫一个不存在的方法,因此对于 nil 呼叫任何方法都不会产生错误。

物件的所有权

一般来说,如果我们使用了某个 initWith…开头的方法来初始化物件的话,我们必须记得 retain 这些传入的参数:

- setName:newName {   if{       [name release];       name = [newName retain]; // 物件的 memory count 加一   } }

然而,这会面临一个问题是,传入的 newName 跟物件自己拥有的 name 两个其实是在记忆体中指向同一个物件,所以如果在物件中对 name 这个变数做内容的修改,一样会影响到当初传入的 newName。所以针对这个问题,Objetive-C 也提供了相对的解决方式::

- setName:newName {   if{       [name release];       name = [newName copy]; //在记忆体中另外複製了一份 newName,并且 memory count 为 1   } }

这样的话我们就算修改物件 name 变数的内容,那也并不会影响到本来传入的参数。

实做 dealloc

当 A 物件如果 retain 了 B 的话,那我们在 A 物件被 dealloc 的时候需要一同 release B 物件,这样才不会造成 memory count 无法归零的状况,以下是一个常见的作法:

- dealloc { // 先处理本身所拥有的物件变数   [name release]; // 当处理完之后就可以呼叫父类别的 dealloc 方法   [super dealloc]; }autorelease

CS193P第三堂课摘要及心得笔记

在我们呼叫物件的方法时,这个方法可能需要会回传另外一个物件给我们,像是以下的範例:

- getSomething {   NSString *result = [[NSString alloc] initWithString: @"Something"];    return result; }

然而,像是以上这样的範例会造成记忆体上的问题,因为我们在建立了 result 并且回传之后,就不会在用到这个物件了,但是我们却没有 release 这个物件。所以,你可能会想要这样写:

- getSomething {   NSString *result = [[NSString alloc] initWithString: @"Something"];   [result release];   return result; }

可惜,这样的写法也是有问题的,因为回传的 result 物件再回传之前就先 release、memory count 归 0 了,所以再回传之前就先在记忆体中被删除了!针对这个问题,Objective-C 有一个特别的机制可以帮助我们,也就是 autorelease。我们之需要将 [result release]; 这行换成 [result autorelease]; 就可以解决这个问题了!

而这样方便的功能是怎样完成的呢?或许大家已经注意到,在之前每个作业中的程式码通常都会以下列的形式呈现:

int main {    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];   // do something   [pool release]; }

这边的 NSAutoreleasePool 就是帮助我们完成这个功能的重要角色,他会将所有被最近 autorelese 的物件收集起来,当这个 pool 本身被 release 的时候,他就会把这些物件一一 release 掉。而 UIKit 针对每一个处理的事件会自动的用一层 AutoreleasePool 包起来,所以每当一个 UI 事件处理完成的时候,这些物件都会相继被 release 掉。

命名惯例

我们都知道了 autorelease 是如此的方便,而在 Cocoa 的 API 中有一些命名习惯就是与记忆体管理息息相关的:

我会建议各位读者在自行撰写方法的时候,也要尽量遵循这些命名惯例,以讲求未来使用上的一直性。

@property

在物件导向一般的开发流程中,我们需要针对物件实做很多方法来让使用者能够操作物件中的变数。假设我们的物件定义拥有这些方法:

- name; - setName:value; - age; - setAge:age; - canLegallyVote;

但我们可以注意到,这些方法其实都是为了读取或是操作物件内的变数而所产生的方法。透过@property 的语法,我们可以写成:

@property int age; @propertyNSString *name; @propertyBOOL canLegallyVote;

是不是简洁很多呢?事实上,如果当我在.h 档中宣告了:

@property int foo;

其实就等同于我写了:

- foo; - setFoo:value;

而在.m 档中,我们也不需要一一实做这些@property 所产生的方法,我们只要写:

@synthesize foo;

就会等同于产生了:

-foo { return foo; }-setFoo:value { foo = value; }

透过@property 和@synthesize 就可以减少很多这类重複的工作,这样方便的功能可得牢记在心!

但需要注意的是,@property 并非一定要搭配@@synthesize 使用,你可以只在.h 档中宣告@property 而在.m 档中自行实做方法,而不透过系统自动产生。这样做的原因通常是你需要在修改物件变数内容时做一些检查,检查设定的值是否合法。

Property 的属性

除了单纯简化变数的存取之外,@property 也支援了许多不同的属性。像是设定了属性的@property 只会产生读取用的方法、不会有设定变数的方法产生。而针对记忆体管理的部份,Obj-C 提供了三种不同的属性,分别是 、 和 。

一般的@property 在没有设定属性的状况下预设值为 ,而属性会直接传入的参数物件直接指派给物件的变数,这样会造成两个问题。一个是没有 retain、另外一个是物件变数的内容及传入的参数其实两个是指到相同的内容。

因为上面这两个问题,分别有了和两个参数来解决,搭配的 property 会在@synthesize 的时候一併对传入的物件 retain,而则是会对传入物件另外拷贝一份。

点号和 self

当我们针对某个物件变数设定了@property 属性之后,我们就可以使用像 foo.name 这样的方式来取用变数,或是用 foo.name = @"Apple"; 这样来设定变数。

但是需要注意的是,当我们在实做物件的时候可千万要分清楚两者:

@implementation Person - doSomething { name = @“Fred”; // 直接设定物件变数 self.name = @“Fred”; // 呼叫 setName 这个方法 }

错误的使用可能会造成无穷迴圈:

- setName: value { self.name = value; // 这样又会呼叫 setName 这个函式 }结论

在漫长的基础训练之后,我们终于把 Obj-C 中比较困难的部份学完了!也就是我们已经具备了足够的基础,接下来就是开发 iPhone App 了!在下一次的连载中,我们将介绍 iPhone 上的介面开发,还请大家继续指教!

参考资料