在C++中,没有属性的概念,只有setter,getter的方式来对类成员变量进行操作。
如:
class gloox
{
public:
gloox(){};
~gloox(){};
int getCount(){return m_count;};
void setCount(int acount){m_count = acount;};
private:
int m_count;
}
再来看Object-C中的设计。
.h文件
#import <Foundation/Foundation.h> @interface OCDeomer : NSObject { NSInteger ct; } -(void)setCount:(NSInteger) tcount; -(NSInteger)count; @end
.m文件
@implementation OCDeomer-(void)setCount:(NSInteger) tcount { ct = tcount; }
-(NSInteger)count { return ct; }
@end
调用,点语法的方式:
- (IBAction)onremove:(id)sender {
OCDeomer* dm = [[OCDeomer alloc]init];
//OC的调用方式
[dm setCount:20];
NSInteger tc = [dm count];
NSLog(@"tc = %i",tc);
//点语法的访问方式
dm.count = 40;//默认调用setCount 即setter方法。 tc = dm.count;//默认调用getter方法。 NSLog(@"tcc = %i",tc); [dm release]; }
上面是手动写的setter,getter方法
在OC中,@property 类型 方法名 在编译的时侯会自动的展开为setter getter 的方式。展开的规则:展开为setter时,默认为set+方法名(首字母大写),getter时,方法名跟属性名一样。如上面的例子:
@property NSInteger count;编译时展开为下面的setter 和getter方法。
-(void)setCount:(NSInteger) tcount;
-(NSInteger)count;
修改成如下形式,再进行测试,如果一样。
#import <Foundation/Foundation.h> @interface OCDeomer : NSObject { NSInteger ct; } //-(void)setCount:(NSInteger) tcount; //-(NSInteger)count; @property NSInteger count; @end
光有属性还没完事,OC还为我们节省了不少setter 和getter的实现方式。如:
@synthesize count;
这句就相当于下面这两个方法的实现:
-(void)setCount:(NSInteger) tcount
{
}
-(NSInteger)count
{
}
了解这些特性之后,就可以进行组合使用,如
1、使用@property 和@synthesize
2、也或以使用@property 和setter,getter的实现。
3、或setter,getter的声明和@synthesize的组合
还有一点需要注意的是self.这个访问操作。
self.count 与[self count] 实际上是操作属性,对属性的操作最终都会转为setter,getter操作,与在类中调用类成员是有区别的。
因此在处理类成员变量时,当成员名字与属性名字相同的时候,就要相当注意self.与没有self.时的操作了。
下面举个例:
#import <Foundation/Foundation.h> @interface OCDeomer : NSObject { UIImage *img; } @property (retain) UIImage *img;//注意这里属性名与类成员变量名相同,注意这里还使用了retain关键词 -(void)test;//主要用于测试self.操作所产生的BUG @end 实现部分
#import "OCDeomer.h" @implementation OCDeomer @synthesize img;//setter ,getter的实现 -(id)init { self = [super init]; if (self) { //init to do . UIImage* _img = [[UIImage alloc]init]; self.img = _img;//这句话很关键,如果修改为img = _img;哪么后面调用test就会CRASH,因为出了init,_img释放了。 [_img release]; } return self; } -(void)dealloc { [super dealloc]; } -(void)test { NSLog(@"width = %f",img.size.width); } @end
分析:如果简单的将img = _img;即指向_img;但出了init方法的时候指针释放了,哪么在调用test时,这个时候img就为空指针了,当然就错了。哪为什么加上self.就正确了呢,关键就在于retain这个关键词。(assign,retain,copy)后面逐步分析。
在什么情况下self.与没有使用self.的操作时效果一样,哪就是属性使用关键词assign时。
关键词:
readwrite 读写操作,默认生成setter ,getter 方法。类似DELPHI中的property xxx:integer read getxxx write setxxxx;
readonly 只读操作。类似DELPHI中的property xxx:integer read getxxx;
assign 赋值操作,这个属性一般用来处理基础类型,比如int、float等等,如果你声明的属性是基础类型的话,assign是默认的,你可以不加这个属性。
其setter,getter 的实现:
如上例子的count属性。
-(void)setCount:(NSInteger) tcount
{
ct = tcount;//直接赋值。
}
-(NSInteger)count
{
return ct;
}
retain 引用计算+1操作(针对NSObject的对象集合);retain只是引用计数加1,但内存地址实际上只有一个,与assign 最主要的区别就在于setter方法。
-(void)setName:(NSString*) aName
{
if (name!=aName)
{
[name release];
name = [aName retain];
}
}
对于retain的属情,在dealloc 方法中,使用[变量 release];变量 = nil;的方式进行释放,但也可以使用self.变量=nil;实际是[self set变量:nil],再进到方法体内可以看到nil retain,这个操作实际上是不起作用。
copy 浅拷贝,如果要实现深考贝需要自己实现COPY方法,该关键词限定的类型必须是实现了copy 或mutablecopy。否则不能将属性设置为copy;这个会自动生成你赋值对象的克隆,相当于在内存中新生成了该对象的副本(新的内存地址),这样一来,改变赋值对象就不会改变你声明的这个成员变量。
ios中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying 协议的类可以发送copy消息,遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。假如发送了一个没有遵守上诉两协议而发送 copy或者 mutableCopy,那么就会发生异常。但是默认的ios类并没有遵守这两个协议。如果想自定义一下copy 那么就必须遵守NSCopying,并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法。
还要注意一点,当copy用在不可变的类型上时,效果就相当于retain,只有在可变的类型上时,才是真正意义的地址COPY。
copy 属性的setter 实现:
-(void)setName:(NSString*) aName
{
[name release];
name = [aName copy];
}
举个例子:
#import <Foundation/Foundation.h> @interface OCDeomer : NSObject { NSString *_name; NSString *_phone; } @property (retain) NSString *name;//retain 属性 @property (copy) NSString *phone;//copy 属性 @end
-(void)setName:(NSString *)name { [name retain]; [_name release]; _name = name; } -(NSString*)name { return _name; } -(void)setPhone:(NSString *)phone { [_phone release]; _phone = [phone copy]; } -(NSString*)phone { return _phone; }
调用:
- (IBAction)onset:(id)sender { OCDeomer* dm = [[OCDeomer alloc]init]; //NSMutableString *string = [NSMutableString stringWithString:@"Hello"];//使用这个时COPY属性才有效。 NSString *string = [[NSString alloc]initWithString:@"hello"];//不可变类型,COPY属性=retain [dm setName:string]; [dm setPhone:string]; NSLog(@"name = %@,phone = %@",dm.name,dm.phone); string = [string stringByAppendingString:@"ko"]; //[string appendString:@"world"]; NSLog(@"name = %@,phone = %@",dm.name,dm.phone); [string release]; [dm release]; }
其实倒底是不是这个结论我还差点底,因为我在跟踪过程中,使用p 打印查看地址,发现不可变的字符每次在重新赋值时,地址是变化的。
为了搞懂这个COPY为地址而不是引数+1;我特写了一个例子进行演示:
#import <Foundation/Foundation.h>
@interface CopyDemo : NSObject<NSCopying,NSMutableCopying>//继承NSCopying,NSMutableCopying
@property (nonatomic) NSInteger age;
@end
#import "CopyDemo.h" @implementation CopyDemo @synthesize age; - (id)copyWithZone:(NSZone *)zone { CopyDemo *copy = [[[self class] allocWithZone:zone] init]; copy->age = age; return copy; } - (id)mutableCopyWithZone:(NSZone *)zone { CopyDemo *copy = NSCopyObject(self, 0, zone); copy->age = age; return copy; } @end
演示类:
#import <Foundation/Foundation.h> #import "CopyDemo.h" @interface OCDeomer : NSObject { CopyDemo * copydemo; CopyDemo * retainDemo; } @property (copy) CopyDemo *copydemo;//COPY 属性 @property (retain) CopyDemo *retainDemo;//Retain 属性 @end实现部份:#import "OCDeomer.h" @implementation OCDeomer @synthesize retainDemo; @synthesize copydemo; @end
调用测试结果:- (IBAction)onset:(id)sender { OCDeomer* dm = [[OCDeomer alloc]init]; CopyDemo * demo = [[CopyDemo alloc]init]; demo.age = 30; [dm setRetainDemo:demo]; [dm setCopydemo:demo]; NSLog(@"retain.age = %i,copy.age = %i",dm.retainDemo.age,dm.copydemo.age); demo.age = 40;//retain 的话,修改值,将会改变dm内部的值。即外部修改影响到了类的内部值。 NSLog(@"retain.age = %i,copy.age = %i",dm.retainDemo.age,dm.copydemo.age); [demo release]; [dm release]; }
结果:
2013-01-27 00:00:21.304 demo[852:207] retain.age = 30,copy.age = 30 2013-01-27 00:00:21.307 demo[852:207] retain.age = 40,copy.age = 30
因此在实际操作过程中,对retain和copy 属性的使用需要谨慎,如果对属性设置后,不想后续的修改影响到已传给属性的值,就使用COPY,如果想外部改变影响到内部的值的变化时,就使用retain。但没有绝对的写法,只有在实际中谨用才能把质量提高。同样在dealloc中可以使用self.属性=nil来释放内存。
nonatomic 非原子操作,默认情况下是原子的。
为什么验证属性的多线程操作,在设置为nonatomic,同时加入日志跟踪。
@property (nonatomic) NSInteger count;
-(void)setCount:(NSInteger) tcount
{
NSLog(@"Access in");
ct = tcount;
NSLog(@"Access out");
}于是建立一个线程进行访问NSInteger aa = 1000;
while (aa>0) {
[NSThread detachNewThreadSelector:@selector(thread1) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(thread2) toTarget:self withObject:nil];
aa--;
}-(void)thread1
{
demoer.count = 30; //demoer为全局的。
}
-(void)thread2
{
demoer.count = 40;
}结果:
2013-01-27 00:32:23.188 demo[1045:f9e03] Access in
2013-01-27 00:32:23.246 demo[1045:fb203] Access in
2013-01-27 00:32:23.250 demo[1045:ed603] Access out
2013-01-27 00:32:23.250 demo[1045:ee003] Access out
2013-01-27 00:32:23.251 demo[1045:eea03] Access out
2013-01-27 00:32:23.251 demo[1045:ef403] Access out
2013-01-27 00:32:23.149 demo[1045:f9403] Access in
2013-01-27 00:32:23.252 demo[1045:efe03] Access out
2013-01-27 00:32:23.252 demo[1045:f1203] Access out
2013-01-27 00:32:23.259 demo[1045:f0803] Access out
2013-01-27 00:32:23.260 demo[1045:f1c03] Access out
2013-01-27 00:32:23.260 demo[1045:f2603] Access out
2013-01-27 00:32:23.261 demo[1045:f3003] Access out
2013-01-27 00:32:23.261 demo[1045:f3a03] Access out
2013-01-27 00:32:23.262 demo[1045:f4403] Access out
2013-01-27 00:32:23.262 demo[1045:f5803] Access out
2013-01-27 00:32:23.263 demo[1045:f6203] Access out
2013-01-27 00:32:23.263 demo[1045:f4e03] Access out
2013-01-27 00:32:23.264 demo[1045:f7603] Access out
2013-01-27 00:32:23.265 demo[1045:f8003] Access out
2013-01-27 00:32:23.264 demo[1045:f6c03] Access out
2013-01-27 00:32:23.266 demo[1045:f8a03] Access out
2013-01-27 00:32:23.270 demo[1045:fbc03] Access in
2013-01-27 00:32:23.272 demo[1045:fb203] Access out
2013-01-27 00:32:23.271 demo[1045:f9e03] Access out
2013-01-27 00:32:23.293 demo[1045:f7607] Access in
2013-01-27 00:32:23.277 demo[1045:f9403] Access out
从上面看不是一进一出的,即多线程访问不安全的。哪好,同样我把属性改为了原子操作。即,改为
@property (automic) NSInteger count;
同样式测试,我也惊奇的发现,原来还是不安全的。
2013-01-27 02:01:45.504 demo[187:6433] Access out
2013-01-27 02:01:45.522 demo[187:6437] Access in
2013-01-27 02:01:45.531 demo[187:d86f] Access in
2013-01-27 02:01:45.539 demo[187:aa57] Access in
2013-01-27 02:01:45.542 demo[187:6437] Access out
2013-01-27 02:01:45.542 demo[187:d86f] Access out
2013-01-27 02:01:45.543 demo[187:aa57] Access out因此感觉并不像网上所说的使用原子访问时,会自动在属性访问前加上LOCK,完后UNLOCK。但经验证,并非如此。
如果有高手觉察这里有问题,请指教指教了。
总结:
assign 普通的赋值,使用self.和没有是一样的结果。
retain 引用计数+1,使用self.和没有是不一样的,有区别。
copy 地址COPY,使用self.和没有是不一样的,也是有区别的。