OC:属性

96
Kevin丨Wang
2015.07.28 19:17* 字数 1997

目录

  • 属性演变过程
  • 属性定义
  • 属性扩展

属性演变过程

上篇博文《OC学习备忘录:成员变量、方法》中演示了如何声明成员变量,并在方法中使用。
这里继续以WZKPerson类为例,上篇博文中我们声明了一个lisi的对象,并初始化了该对象的name和age两个成员变量。

WZKPerson *lisi=[[WZKPerson alloc]initWithName:@"李四" age:30];
[lisi sayMyInfo];

假如我们想给lisi进行改名字,该怎么办呢?
由于成员变量在类的内部,无法直接进行修改,又该怎么办呢?
一种简单的办法是,可以利用对象方法进行修改成员变量。
首先需要在WZKPerson.h文件中添加如下方法:

-(void)setName:(NSString *)name;
-(void)setAge:(NSInteger)age;

对应的需要在.m文件中实现这两个方法:

-(void)setName:(NSString *)name
{
   _name=name;
}
-(void)setAge:(NSInteger)age
{
    _age=age;
}

最后就可以在调用类中使用这两个方法了。

//设置lisi的name为李斯
[lisi setName:@"李斯"];
//设置lisi的age为90
[lisi setAge:90];
//输出设置name和age信息
[lisi sayMyInfo];

运行效果如下:



第二个运行结果是没有改变lisi的name和age之前的结果,第三个结果是改变之后的运行结果。

通过方法修改成员变量的值还是比较简单的,那么问题又来了,现在我们想获取lisi的name和age值,又该怎么办呢?同样的方式,可以写一个新的方法进行获取。

在WZKPerson.h文件中添加如下方法:

-(NSString *)name;
-(NSInteger)age;

对应的需要在.m文件中实现这两个方法:

-(NSString *)name
{
    return _name;
}

-(NSInteger)age
{
    return _age;
}

最后就可以在调用类中使用这两个方法了。

NSString *lisiName=[lisi name];
NSInteger lisiAge=[lisi age];
NSLog(@"lisiName= %@ , lisiAge= %li",lisiName,lisiAge);

运行效果如下:


这种通过定义方法的方式访问类的成员变量,其实就是利用到了面向对象中封装特性

到现在为止,通过定义方法的方法的方式设置和获取成员变量的值看起来没有什么问题,但是会引出一个问题。
假如又定义了一个NSMutableString类型的变量personName,将personName值赋值给wangwu的name变量。

NSMutableString *personName=[NSMutableString stringWithString:@"王五"];
[wangwu setName:personName];
NSLog(@"personName= %@,wangwu's name=%@",personName,[wangwu name]);

输出结果肯定是:“personName= 王五,wangwu's name=王五”。
现在我希望personName值为“王五123”,wangwu 的name值还是为“王五”。可能我们会用下面代码来改变personName的值。

[personName appendString:@"123"];

添加完这行代码后,重新运行一下,发现输出的结果为:“personName= 王五123,wangwu's name=王五123”,和我们预期的结果不一样,这是什么原因造成呢?
还记得在WZKPerson.m文件中的这两段代码吗?

-(id)initWithName:(NSString *)name age:(NSInteger)age
{
    self=[super init];
    if (self) {
        _name=name;
        _age=age;
    }
    return  self;
}
-(void)setName:(NSString *)name
{
    _name=name;
}

造成的原因是因为_name=name导致的,这又是为什么呢?
这里先来看一下personName和[wangwu name]两个值的内存地址。

NSLog(@"%p",personName);
NSLog(@"%p",[wangwu name]);

运行后,发现personName内存地址为0x100300aa0,另外[wangwu name]也是0x100300aa0,这两个内存地址一样,说明通过_name=name赋值方式,[wangwu name]的内存地址指向了personName的内存地址,这种方式叫做浅拷贝。

终于找到原因了,那有没有解决办法来实现我们的预期结果呢?
当前可以了,既然有浅拷贝,那必然会有一个叫深拷贝(copy)的东东。那么怎么使用copy呢?很简单,直接看代码。

-(id)initWithName:(NSString *)name age:(NSInteger)age
{
    self=[super init];
    if (self) {
        _name=[name copy];
        _age=age;
    }
    return  self;
}
-(void)setName:(NSString *)name
{
    _name=[name copy];
}

只需要将原来的_name=name代码改成_name=[name copy]就可以了。
重新运行一下,发现输出的结果变为:“personName= 王五123,wangwu's name=王五”,达到了预期结果。
再来看一下内存地址,验证一下是不是指向两个不同的内存地址。
发现personName内存地址变成了0x1003004d0,而[wangwu name]内存地址变成了0x100300a90。
内存地址也不一样,完全达到了预期结果。

age成员变量由于是NSInteger类型,不会涉及深拷贝和浅拷贝

但是问题又来了,我们发现,仅仅两个简单成员变量的设置和获取操作,就需要写十几行的代码,如果一个类里面有十几个变量,那么仅仅这一块代码就需要上百行,严重影响了开发效率,并且错误率也会大大的提升。

属性定义

苹果公司在Objective-C 2.0中引入了属性(property),它组合了新的预编译指令和新的属性访问器语法。新的属性功能显著的减少了冗余代码的数量。

怎么在上述代码中添加WZKPerson的name和age的属性的呢?

首先我们先将WZKPerson类.h和.m文件中:
-setName:、-setAge:、-name:和-age:方法的声明和实现注释掉。

在WZKPerson.h文件添加如下代码。

@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;

最后运行一下程序,发现输出的结果和之前是一样的。


另外也可以通过点方式设置或者获取属性值。

wangwu.name=@"12345";//等价于[wangwu setName:@"12345"];
wangwu.age=20;//等价于[wangwu setAge:20];
[wangwu sayMyInfo];

到此为止,我们就成功的添加了WZKPerson类的name和age的属性,是不是很简单。

下面稍微解释属性声明的代码:

  • OC中可以通过@property声明属性;
  • 通过@synthesize自动生成getter、setter方法(目前可以省略@synthesize声明);

属性声明后,甚至也可以直接省略掉成员变量的声明

下面总结一下属性的生成规则:

  1. 如果只声明了一个属性a,不使用@synthesize实现,编译器会使用_a作为属性的成员变量。
    如果没有定义成员变量_a,则会自动生成一个私有的成员变量_a;
    如果已经定义了成员变量_a则使用自定义的成员变量_a;
    注意,如果此时定义的成员变量不是_a而是a,则此时会自动生成一个成员变量_a,它和自定义成员变量a没有任何关系;
  2. 如果声明了一个属性a,使用@synthesize a进行实现,但是实现过程中没有指定使用的成员变量,则此时编译器会使用a作为属性的成员变量。
    如果定义了成员变量a,则使用自定义成员变量;
    如果此时没有定义,则自动生成一个私有的成员变量a;
    注意,如果此时定义的是_a,则它跟生成的a成员变量没有任何关系;
  3. 如果声明了一个属性a,使用@synthesize a=_a进行实现,这个过程已经指定了使用的成员变量,此时会使用指定的成员变量作为属性变量;

属性扩展

可能大家会发现,在上面声明属性代码中有一个()号,并且其中有一些参数。

@property(nonatomic,copy)NSString *name;
@property(nonatomic,assign)NSInteger age;

()号中的参数,我们叫做属性限定词。
这些限定词包括:nonatomic、atomic、copy、retain、strong、weak、assign、 readonly、readwrite;
下面简单的介绍这些限定词的含义:

限定词 说明
atomic 默认值,表示原子,同一时间可以由多个线程访问属性
nonatomic 表示非原子,同一时间只能有一个线程进行访问
readonly 只读属性,只生成getter方法
readwrite 默认值,生成getter、setter方法
assign 默认值,直接赋值,通常修饰基本数据类型,也可以修饰对象数据类型,但此时会指向同一个内存地址
retain 先release原来的值,再retain新值,会导致引用计数+1
copy 先release原来的值,再copy新值(深拷贝)
strong iOS4.0之后出来的关键字,会导致引用计数+1,作用等价于retain
weak iOS4.0之后出来的关键字,用于对象数据类型,作用等价于assign

@property的参数最多可以有三个,中间用逗号分割。如果不进行设置,程序会使用默认参数:(atomic,readwrite,assign)

一般情况下,如果在多线程开发中一个属性可能会被多个线程同时访问,可以考虑用atomic,否则建议使用nonatomic,效率更高;

小技巧:通常情况下,字符串对象使用copy,非字符串对象使用retain,基本数据对象使用assign;
另外使用copy功能需要一些前提,需要遵守NSCopying协议,实现copyWithZone:方法;

Objective-C
Gupao