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 总是推动大家用新版本的东西。
1 2 3 4 5 6 7 8 9 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:
1 2 3 4 5 6 7 8 9 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 54 55 56 57 |
#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 方法的内部实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//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 程序的执行效果完全是一样的。
本文链接 https://yanbin.blog/objective-c-proerty-synthsiz/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
楼主的 测试结果有问题,测试方法有问题:正确的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,而不是楼主所说的 在实例方法中不起作用。
应该是:
-(
void
) setAddress: (Address *) address {
if(_address){
[
_
address release];
}
_address = [address retain];
}
//retain 时:
-(
void
) setAddress: (Address *) address {
[
self
.address release];
//self.address = nil;
self
.address = address;
}
楼主 你确信是这样吗? 确信是self.吗?
希望与贵站友链,本人也在学iphone开发
没问题的,我去再加一个widget
哥们,字号和英文字体太影响阅读了
这么快就有评论了,这种英文字体有点花,确实有些影响阅读了,要放大网页来看。