在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;
{
NSLog(@"Access in");
ct = tcount;
NSLog(@"Access out");
}
while (aa>0) {
[NSThread detachNewThreadSelector:@selector(thread1) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:@selector(thread2) toTarget:self withObject:nil];
aa--;
}
{
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.和没有是不一样的,也是有区别的。