IOS 的属性剖析(点语法操作)

来源:互联网 发布:打碟软件 编辑:程序博客网 时间:2024/05/16 15:18

在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 = 302013-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.和没有是不一样的,也是有区别的。

 

 

原创粉丝点击