Objective-C 的属性与合成方法使用详解
Objective-C 中的属性可以结合 Java 和 C# 的属性来理解,Java 的属性需要自己实现 getter/setter 方法,在 C# 中现在可方便些了,写上{set;get;}自动生成相应的存取器。
Objective-C 中声明属性及使用时会涉及到 @property, @synthesize 和点号(.) 访问,@property 用来指定属性及某些特性,@synthesize 能为你用 @property 指定的属性自动生成 getter/setter 方法。下面最常规的例子:
main.m 代码:本例在 Xcode 4.0.2 中编译运行的, 可能涉及到一些是 Objective-C 的新特性,Apple 总是推动大家用新版本的东西。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
#import <Foundation/Foundation.h @interface Unmi : NSObject { NSString * gender; //1 -- 这行可以不用的 } @property ( nonatomic , assign) NSString * gender; //2 @end @implementation Unmi @synthesize gender; //3 @end int main ( int argc, const char * argv[]) { NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc] init]; Unmi* unmi = [[Unmi alloc] init]; unmi.gender = @"Male" ; //4 NSLog ( @"Unmi is %@!" , unmi.gender); //5 [pool drain]; return 0; } |
上面代码的输出是:
2011-07-01 01:19:09.344 TestObjC[5502:903] Unmi is Male!
下面一些说明,分别对应到上面的几个代码行上的标号:
//1. 一般属性会对应一个类的实例变量,用来保存状态的, 而实际上把该行代码注释掉也可以输出相同的结果,也就是这行代码可有可无,有它后面的 @property 就够了。
//2. 指明属性变量,类型及一些其他特性,这里的特性就比较多了,你可以简单写为 @property NSString* gender; 会采用默认值,但编译时会出现警告,不建议这么做。
这里如果没有显式的声明一个 NSString * gender 成员变量,它会帮你补上与这里的属性同名成员变量。
即使是 IBOutlet 的属性也可以不用显式的声明这个成员变量,同样写在 @property 中就行的:
如:@property(nonatomic, retain) IBOutlet UIButton *button_screen;
关键是括号中的内容:当然,最好的教程莫过于 官方的权威 Declared Properties.
@property 括号中的属性用逗号分隔来写, 对于对象 (atomic, assign) 是它的默认值, 基本类型默认为 (atomic, readwrite), 有三组值可以设置,互斥的就不要写在一起:
1) atomic 和 nonatomic, 原子还是非原子性操作,前者为默认,表示属 性是原子的,支持多线程并发访问(实际就是 setter 的实现中加入了同步锁),后者是非原 子的,也就是适合在非多线程的环境提升效率(因为 setter 中没有同步锁的代码)。没有特别的多线程要求用 nonatomic 有助于提高性能。
2) readonly, readwrite 表示属性的可读写特性;
如果是对象类型,还有 retain, assign, copy, 这决定了 setter 方法内部实现时对传入的对象的持有方式。retain 会增加引用计数,强引用类型, assign 是给变量直接赋值,弱引用类型,也是默认值, copy 是把 setter 的参数复制一份再赋给成员变量。注意它们对引用计数产生的影响,如果外部不再使用的话,用了 retain 或 copy 赋值的可以
release 掉那个对象。
3) getter=getterName 和 setter=setterName, 显式设置 getter/setter 方法名, 未指定它们时 Objective-C 会为我们生成默认的 setter/getter 方法, 有一定的规则,
比如上面的 NSString* gender 属性生成默认的
setter 方法是: -(void) setGender:(NSString *);
getter 方法是: -(NSString *) gender;
想看看 Objective-C 为我们生成什么 getter/setter 方法, 不用点号来隐式调用 setter/getter 方法,而是显式的用 [unmi setGender] 或 [unmi gender], 输入式这两个方法会自动提示出来的。注意这里的 getter 方法名并非是像 Java 的 getGender, 而是和属性名同.
假如你想要自己个性的 getter/setter 方法,比如写成 @property(getter=getGender, setter=setSex:) NSString* gender; 那么相应的就会生成:
setter 方法是: -(void) setSex:(NSString *);
getter 方法是: -(NSString *) getGender;
在 Xcode 中 esc unmi 就能看到相应的 setter/getter 方法名的. 可以只用其中一个了,那另一个保持默认。这两个较少用,用途就是可用来生成自己个性的但要符合某个范围内规范的 setter/getter 方法。像 @property(getter = isOnline) BOOL online; 则会生成 -(BOOL) isOnline; 这样的 getter 方法,而不是 -(BOOL) online; 当然我们也很少且不推荐直接调用 getter/setter 方法,而是用点号的方式,但是有意思的去覆盖 getter/setter 方法时就较象明确了。
//3. @synthesize 后跟上前面用 @property 声明的属性名列表,这样 Objective-C 就能自动按照 @property 规则生成相应的 setter/getter 方法。你也可以不对前面某个属性使用 @synthesize,那么它相应的 setter/getter 方法就得自己按照规则亲自实现了。
所以,到这里我们可以理解到,@property 相当于声明 setter/getter 的方法原型,@synthesize 就是那些 setter/getter 相应实现。只是它们俩都自动完成了,连存储状态的变量也自动添加了。
前面讲过,如果类中没有声明与 @property 相应成员变量,会自动加上一个与属性同名的成员变量,如果你不想要与属性同名的成员变量,这里可以自定义,方法是:
@property gender=_gender;
那就相当于在类中声明了一个 (NSString *) _gender 成员变量来存储 gender 属性的值, 而不再存在 (NSString *) gender 这个成员变量了。这样在类 Unmi 实例方法中可以直接访问 _gender 变量的. 另外,据我刚刚试验过的,用 @property gender=_gender; 自动生成的成员变量 (NSString *) _gender 同样可以在断点时光标停在某个 Unmi 实例上能显示出来的。
接着,这里又会牵涉到 @dynamic 的用法,当 @property(getter=getGender) 只为 gender 指定了 getter 方法名时,而后不用 @synthesize 自动合成,而是自己实现的 -(NSString *) getGender; 方法,编译器会警告 setGender 未实现,这时就用 @dynamic gender, 此处不细究 @dynamic 的用法了。
//4. 用点号(.) 来使用属性,这和 C# 中的属性较类似了,凡是对属性进行赋值,会调用相应的 setter 方法,这里调用 -(void) setGender:(NSString *);
//5. 点号获取属性值时,实际调用了相应的 getter 方法,这里调用了 -(NSString *) gender;
这里的例子是通过实例变量来使用属性,读写时分别会走 getter/setter 方法,然而在类的内部可以直接访问该成员变量,也可以用点号属性的方式, 在类内部怎么访问都无所谓的,来看下面的例子,变动了一下:
main.m:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
#import <Foundation/Foundation.h> @interface Address : NSObject @end @implementation Address @end @interface Unmi : NSObject { //Address * address; //声明或不声明这个都一样 --1 } @property ( nonatomic , retain) Address * address; //有 retain 特性 -( void ) foo; @end @implementation Unmi @synthesize address; //@synthesize address=address; //通过这里来命名一个成员变量也无妨 --2 -( void ) foo{ Address * newAddress = [[Address alloc] init]; NSLog ( @"1. newAddress retain count: %lu" , [newAddress retainCount]); address = newAddress; //直接访问成员变量 NSLog ( @"2. newAddress retain count: %lu" , [newAddress retainCount]); self .address = newAddress; //self. 的方式 NSLog ( @"3. newAddress retain count: %lu" , [newAddress retainCount]); [ self setAddress:newAddress]; //直接调用 setter 方法,与上面其实是一致的 NSLog ( @"4. newAddress retain count: %lu" , [newAddress retainCount]); } @end int main ( int argc, const char * argv[]) { NSAutoreleasePool * pool = [[ NSAutoreleasePool alloc] init]; Unmi* unmi = [[Unmi alloc] init]; [unmi foo]; Address * newAddress = [[Address alloc] init]; NSLog ( @"5. newAddress retain count: %lu" , [newAddress retainCount]); unmi.address = newAddress; NSLog ( @"6. newAddress retain count: %lu" , [newAddress retainCount]); unmi.address = newAddress; NSLog ( @"7. newAddress retain count: %lu" , [newAddress retainCount]); unmi.address = [[Address alloc] init]; NSLog ( @"8. newAddress retain count: %lu" , [newAddress retainCount]); [pool drain]; return 0; } |
执行结果是:
2011-07-01 04:08:17.483 TestObjC[7532:903] 1. newAddress retain count: 1
2011-07-01 04:08:17.492 TestObjC[7532:903] 2. newAddress retain count: 1
2011-07-01 04:08:17.493 TestObjC[7532:903] 3. newAddress retain count: 1
2011-07-01 04:08:17.497 TestObjC[7532:903] 4. newAddress retain count: 1
2011-07-01 04:08:17.498 TestObjC[7532:903] 5. newAddress retain count: 1
2011-07-01 04:08:17.502 TestObjC[7532:903] 6. newAddress retain count: 2
2011-07-01 04:08:17.502 TestObjC[7532:903] 7. newAddress retain count: 2
2011-07-01 04:08:17.504 TestObjC[7532:903] 8. newAddress retain count: 1
address 属性用了 retain 来修饰,从上面的输出可看到在实例方法中无论通过什么方式访问 address 属性都不会增加参数的引用计数,所以在类内部想用 对实例变量直接赋值或是通过属性来赋值都无所谓,效果是一样的,只有在外部通过实例来调用方法时才会使引用计数加 1.
启用这段代码中的 --1 或是 --2 也是一样的效果。
也提一下 @property 中的 retain/assign/copy 对应 setter 方法的内部实现:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
//retain 时: -( void ) setAddress: (Address *) address { [ self .address release]; //self.address = nil; self .address = address; } //assign 时: -( void ) setAddress: (Address *) address { self .address = address; } //copy 时: -( void ) setAddress: (Address *) address { [ self .address release]; self .address = [address copyWithZone: zone]; // Address 必须实现 NSCoping 协议 } |
看到前面用 %lu 来输出对象的 retainCount 也知道是一个 Mac OS X 的程序,因为 GC 的因素,也许会怀疑在 iPhone/iPad 下会得到不同的结果,我试过了,改成 %u 来输出 retainCount,做成 iOS 程序的执行效果完全是一样的。