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 程序的执行效果完全是一样的。

posted @ 2011-08-03 14:21  程序是啥  阅读(3943)  评论(0编辑  收藏  举报