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 总是推动大家用新版本的东西。

#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:

#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;    //后不带等号,会自动声明一个成员变量,且与属性同名,即 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]);
    _address = newAddress; // 当 address 已有值时直接访问成员变量
    NSLog(@"4. newAddress retain count: %lu", [newAddress retainCount]);
    [self setAddress:newAddress]; //直接调用 setter 方法,与 self.address 其实是一致的
    NSLog(@"5. newAddress retain count: %lu", [newAddress retainCount]);
    
    printf("\n");
}
@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(@"6. newAddress retain count: %lu", [newAddress retainCount]);
    unmi.address = newAddress;
    NSLog(@"7. newAddress retain count: %lu", [newAddress retainCount]);
    unmi.address = newAddress;
    NSLog(@"8. newAddress retain count: %lu", [newAddress retainCount]);
    unmi.address = [[Address alloc] init];
    NSLog(@"9. newAddress retain count: %lu", [newAddress retainCount]);
    
    [pool drain];
    return 0;
}

执行结果是:

2011-08-23 09:29:07.931 TestObjC[25620:707] 1. newAddress retain count: 1
2011-08-23 09:29:07.935 TestObjC[25620:707] 2. newAddress retain count: 1
2011-08-23 09:29:07.935 TestObjC[25620:707] 3. newAddress retain count: 1
2011-08-23 09:29:07.936 TestObjC[25620:707] 4. newAddress retain count: 1
2011-08-23 09:29:07.936 TestObjC[25620:707] 5. newAddress retain count: 1

2011-08-23 09:29:07.937 TestObjC[25620:707] 6. newAddress retain count: 1
2011-08-23 09:29:07.937 TestObjC[25620:707] 7. newAddress retain count: 2
2011-08-23 09:29:07.938 TestObjC[25620:707] 8. newAddress retain count: 2
2011-08-23 09:29:07.938 TestObjC[25620:707] 9. newAddress retain count: 1

address 属性用了 retain 来修饰,从上面的输出可看到在实例方法中无论通过什么方式访问 address 属性都不会增加参数的引用计数,所以在类内部想用 对实例变量直接赋值或是通过属性来赋值都无所谓,效果是一样的; 对于内部使用属性 Objective-C 应该是知道怎么去优化的,不会产生多余的引用计数的。只有在外部通过实例来调用方法时才会使引用计数加 1.

上面的测试代码其实不是很具代表性的,在类内部的方法里尽量还是要用 self. 的方式来赋值,这样才能保证 retain 住实例。

启用这段代码中的 --1 或是 --2 也是一样的效果。

也提一下 @property 中的 retain/assign/copy 对应 setter 方法的内部实现:

//retain 时:
 -(void) setAddress: (Address *) address {
     if(_address){
         [_address release];
     }
     _address = [address retain];
 } 

//assign 时:
 -(void) setAddress: (Address *) address {
     self.address = address;
 } 

//copy 时:
 -(void) setAddress: (Address *) address {
     if(_address){     
         [_address release];
     }
     self.address = [address copyWithZone: zone];// Address 必须实现 NSCoping 协议
 }

看到前面用 %lu 来输出对象的 retainCount 也知道是一个 Mac OS X 的程序,因为 GC 的因素,也许会怀疑在 iPhone/iPad 下会得到不同的结果,我试过了,改成 %u 来输出 retainCount,做成  iOS 程序的执行效果完全是一样的。

类别: iOS. 标签: , . 阅读(3,180). 订阅评论. TrackBack.

Leave a Reply

7 Comments on "Objective-C 的属性与合成方法使用详解"

avatar
richard_lee
Guest
richard_lee

楼主的 测试结果有问题,测试方法有问题:正确的setAddress 函数应该是这样的:- (void)setAddress:(Address *)address
{ if (_address != address) { [timestamp release]; timestamp = [newValue retain]; } 
所以说楼主总结的address 属性用了 retain 来修饰,从上面的输出可看到在实例方法中无论通过什么方式访问 address 属性都不会增加参数的引用计数,所以在类内部想用 对实例变量直接赋值或是通过属性来赋值都无所谓,效果是一样的;
这个有问题。
在函数 -(void) foo中 楼主首先_address = newAddress; 然后self.address = newAddress; 根据正确的setAddress方法 两次的赋值一样就不会retain 当然值是1, 如果楼主先执行self.address = newAddress;就会发现值为2,而不是楼主所说的 在实例方法中不起作用。

abc
Guest

//retain 时:
 -(void) setAddress: (Address *) address {
     [self.address release]; //self.address = nil;
     self.address = address;
 }

楼主 你确信是这样吗? 确信是self.吗?

echodjb
Guest

希望与贵站友链,本人也在学iphone开发

sherrier
Guest
sherrier

哥们,字号和英文字体太影响阅读了

wpDiscuz