Getter/Setter 与Property 语法
Getter 就是用来取得某个实例的某个成员变量的method,setter则是用来设定成员变数。如果某个成员变量是C 的类别,像是int,我们可以这么写。假使我们有个Class 叫做MyClass,成员变数是number:
@interface MyClass:NSObject
{
int number;
}
- (int)number;
- (void)setNumber:(int)inNumber;
@end
我们建立了setter叫做setNumber:
,而getter叫做number
。请注意,在其他语言的习惯中,getter可能会取名叫做getNumber
,但是Objective-C语言的惯例则是只取number
这样的名称。实现则是:
- (int)number
{
return number;
}
- (void)setNumber:(int)inNumber
{
number = inNumber;
}
如果是Objective-C物件,我们则要将原本成员变数已经指向的内存位置释放,然后将传入的实例retain 起来。可能像这样:
- (id)myVar
{
return myVar;
}
- (void)setMyVar:(id)inMyVar
{
[myVar release];
myVar = [inMyVar retain];
}
假如今天我们在开发的应用程序里头用到了很多个thread,而在不同的thread中,同时会用到myVar,这么写其实并不安全:在某个thread中呼叫了[myVar release]
之后,到mvVar指定到inMyVar的位置之间,假使另外一个thread刚好用到了myVar,这时候myVar刚好指到了一块已经被释放的记忆体,于是就造成了EXC_BAD_ACCESS
错误。
要避免这种状况,一种方法是加上一些lock,让程式在呼叫setMyVar:
的时候,不让其他thread可以使用myVar;另外一种简单的方法是,只要一直不要让myVar指定到可能被释放的记忆体位置。我们可以这么改写:
- (void)setMyVar:(id)inMyVar
{
id tmp = myVar;
myVar = [inMyVar retain];
[tmp release];
}
我们先将myVar 原本指向的内存位置,暂存在一个变量中,接着直接将myVar指到传入的内存位置,接着再释放tmp变数中所记住的、原本的内存位置。由于每次都要这么写,写久了会觉得麻烦,通常会写成一个macro,或是直接使用Objective-C 2.0 里头的property 语法。
相信在开始学习开发Mac OS X 与iOS程式的时候,大部分书籍一开始就示范如何使用property语法。也就是,像我们上面的例子,用property 语法可以写成:
@interface MyClass:NSObject
{
id myVar;
int number;
}
@property (retain, nonatomic) id myVar;
@property (assign, nonatomic) int number;
@end
@implementation MyClass
- (void)dealloc
{
[myVar release];
[super dealloc];
}
@synthesize myVar;
@synthesize number;
@end
我们在这边使用了@synthesize
语法,在编译我们的程式的时候,其实就会被编译成我们在上面所写的getter/setter,而我们想要设定myVar的内容时,除了可以呼叫setMyVar:
之外,也可以呼叫dot语法,像是myObject.myVar = someObject
。
我们需要注意,在释放内存的时候,myVar = nil
与self.myVar = nil
这两段代码是不一样的,前者只是单纯的将myVar的指针指向nil,但是并没有释放原本所指向的内存,所以会造成内存泄漏,但后者却等同于呼叫[self setMyVar:nil]
,会先释放myVar原本指向的位置,然后将myVar设成nil。
在这边先补充一下,在Xcode 4.4 之后,如果用了property语法,我们甚至不用定义对应的成员变量,compiler在编译程式的时候,会自动补上myVar 与number 需要对应的成员变数。
偶而我们可以在网路上面听到一些声音,认为初学者应该避免使用property,主要原因除了上述myVar = nil
与self.myVar = nil
这两者容易搞混之外, property的dot语法,又与C的structure语法相同,在还不熟悉的状况下,很容易让初学搞错哪些是property,哪些又是属于一个structure。
例如,我们想要知道一个view的x座标是在哪里,会写出像self.view.frame.origin.x
这种程式,就需要知道,view
是self
的property,frame
也是view
的property,但是x
却是origin
这个CGPoint
里头的变数,而origin
也是frame
这个CGRect
里头的变数,但是初学的时候很容易搞混。
我们想要取得x,可以写成self.view.frame.origin.x
,但想要设定x的位置,如果这么写:
self.view.frame.orgin.x = 0.0;
程式会发生编译错误。self.view.frame.origin.x
其实会被编译成[[self view] frame].origin.x
,这没问题,但是如果要改变view的frame,我们还是要透过setFrame:
,所以即使只是要改变x坐标的位置,我们还是得要这么写:
CGRect originalFrame = self.view.frame;
originalFrame.origin.x = 0.0;
self.view.frame = originalFrame;
其实这两点要搞清楚并不困难,如果因为这些缘故而不使用property语法,实在有些因噎废食,因为使用property语法,可以大幅精简程式码。而由于前述Xcode 4.4可以在宣告property之后,由compiler自动补上对应的成员变数的特性,也不致于会搞错myVar = nil
与self.myVar = nil
的差别—因为我们并没有宣告myVar
,如果写成前者,编译时会产生错误。