从C/C++到Objective-C(四)--- 属性的使用

本文介绍了Objective-C中属性的概念及使用方法,包括如何通过属性简化接口代码,减少重复编码,以及属性中点表达式的使用。同时解释了不同内存管理特性的区别,如assign、retain和copy,并强调了内存管理的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

如果一个类含有很多的实例变量,那么我们将会为这些实例变量编写几乎是一样的setter和getter方法,对于写程序来说这样的重复编码当然是不合理的。所以在oc 2.0中苹果引入了属性,它组合了新的预编译指令和新的访问器语法,可以很好的解决编写实例变量访问方法的问题。

我们来看一个实际的例子,类AllWeatherRadial包含了两个float类型的实例变量,如果没有使用属性的话它的接口代码是这样的:

@interface AllWeatherRadial : Tire
{
    float rainHandling;
    float snowHandling;
}

-(void) setRainHandling:(float) rainHandling;
-(float)rainHandling;
-(void) setSnowHandling:(float) snowHandling;
-(float) snowHandling;
@end;

这样的声明了接口后,在类的实现部分我们将会把实例变量rainHandliing和snowHandliing的访问方法都实现出来,这样写的确挺繁琐的。

接下来我们来看看使用属性的接口代码:

@interface AllWeatherRadial : Tire
{
    float rainHandling;
    float snowHandling;
}
@property float rainHandling;
@property float snowHandling;
@end // AllWeatherRadial
这样的写法就要比原来的方法好多了,最起码少敲了很多代码。这里的@property是一种新的编译器功能,它意味着声明了一个新的对象的属性。 而@property预编译指令的作用就是自动声明属性的setter和getter方法; @property float rainHandling;语句表明AllWeatherRadial类的对象具有float类型的属性,其名称为rainHandling。同时你也可以通过调用-setRainHandling:来设置属性,调用-rainHandling来访问属性。在这里属性的名称与实例变量的名称是一样的,当然我们也可以让它们不一样。稍后再讨论这个问题。

接下来我们看看使用属性后的类实现代码:

@implementation AllWeatherRadial

@synthesize rainHandling;
@synthesize snowHandling;
@end;
这里我们只讨论类的访问方法,所以类的其他代码并没有贴出来,类AllWeatherRadial的初始化方法和其他的一些方法都是一样的。这里的@synthesize也是一种新的编译器功能,它表示创建了该属性的访问代码。比如:@synthesize rainHandling;这行代码表示编译器将添加rainHandling实例变量的访问方法。注意这里我们是把实例变量的声明放在了头文件得接口代码中,当然如果你不声明这些变量(就是上面接口代码大括号中的部分)编译器也会声明的。实例变量的声明我们可以放在两个地方:头文件和实现文件中,甚至还可以拆开来一部分放在头文件中声明,一部分放在实现文件中声明。但是这样有什么区别呢? 情况是这样的,如果你声明的类有一个子类,并且想要直接从子类中通过属性来访问变量,那么变量的声明就必须放在头文件中。如果你确定变量只属于当前类则可以把其声明放在实现文件中。如下:

@implementation AllWeatherRadial
{
    float rainHandling;
    float snowHandling;
}

@synthesize rainHandling;
@synthesize snowHandling;
@end;
上面我们说了属性的名称可以实例变量的名称不一样,那应该怎么实现呢?如下代码所示:

@interface AllWeatherRadial : Tire
{
    float rainHandling;
    float snowHandling;
}
@property float propertyName;  //注意这里
@property float snowHandling;
@end // AllWeatherRadial

//在实现文件中也要做相应的修改
@synthesize propertyName = rainHandling;
这样修改了后编译器仍将创建-setPropertyName:和-propertyName方法,但在其实现代码中用的却是rainHandling实例变量。

接下来我们来看看属性中的点表达式,我们都知道在C或者C++中可以直接通过点(.)来访问struct结构体中的变量,而且C++中对象还可以直接通过点(.)来访问对象中的方法。而在OC中的点(.)表达式只需明白一点,OC中的点表达式是为了更好的简化代码而采用的新特性,点表达式实质上是在调用实例变量的访问方法。如果点表达式出现在等号(=)的左边,那么该变量的setter方法将被调用,如:tire.rainHandling = 20.0;实际上是在调用tire对象中的-setHandling:方法。如果点表达式出现在了等号的右边,则调用的是变量的getter方法,如:float number = tire.rainHandling;实际上是调用了tire对象的-rainHandling方法。关于点表达是在使用的时候还有一些需要注意的地方,我们接着往下看。

在前面我们讨论的是float类型的属性,这样的使用方法也适用于int char BOOL和struct等类型。但是当我们在使用访问方法来访问变量的时候需要保留和释放对象,而且还有一些对象,比如字符串的值我们想要复制(-copy)它们。我们先看下面的代码:

@class Tire;
@class Engine;

@interface Car : NSObject
{
    NSString *name;
    NSMutableArray *tires;
    Engine *engine;
}
@property (readwrite, copy) NSString *name;
@property (readwrite, retain) Engine *engine;

- (void) setTire: (Tire *) tire
         atIndex: (int) index;
- (Tire *) tireAtIndex: (int) index;

- (void) print;

@end // Car

在OC的代码中我们经常都会看到在@property 声明属性那里加了一些相关的特性,这些特性是用来限制属性的。比如这里的name属性就添加了readwrite和copy特性,engine加了readwrite和retain特性,在这里readwrite特性表明编译器会为变量name生成setter和getter方法,当然如果是加了readonly 就只会生成getter方法了。我们重点要说的是这里的copy和retain特性,如果不写的话默认的会是assign特性。

那么这里的copy retain assign都有些什么区别呢?首先assign特性是默认的,指定setter方法采用简单的赋值,比如对一些float int 变量是没有问题的,因为只是简单的赋值所以不会更改变量对应的引用计数,所以assign:就是简单赋值,不更改索引计数(Reference Counting).使用assign: 对基础数据类型 (NSInteger)和C数据类型(int, float, double, char,等),如果变量是对象指针的话就不行了。retain和copy在内存管理中说过它们会自动增加引用计数(+1)。retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1 ,使用retain: 对其他NSObject和其子类 ,retain,是说明该属性在赋值的时候,先release之前的值,然后再赋新值给属性,引用再加1。copy:指定应该使用对象的副本(深度复制),前一个值发送一条release消息。基本上像retain,但是没有增加引用计数,是分配一块新的内存来放置它。copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。copy: 建立一个索引计数为1的对象,然后释放旧对象,copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。当然还有一些其他的特性,如:atomic,nonatomic等,指定原子和非原子特性,同样默认是atomic。retain的做法就类似这样:

-(void) setEngine:(Engine *)newEngine
{
    [newEngine retain];
    [engine release];
    engine = newEngine;
}
对于对象的属性在使用的时候应该特别注意一点,比如在上面接口代码中name属性,name = @"Car" 和self.name = @“Car” 这两个语句有什么区别呢?在使用的时候如果我们用的是前者,也就是没有self点表达式的,那么编译器会认为我们是在给一个名为name的变量进行简单的赋值,当然引用计数是没变的。而采用的是或者带self的语句的话,就表明我们是在调用name的setter方法,也可以写成这样[self setName: @"Car"],这就是点点表达式的妙用。所用在具体使用的时候我们也特别注意一下。还有比较重要的一点:别忘了内存管理,在-dealloc方法中该释放的得记得释放。


参考书籍:《Objective-C 基础教程(第二版)》



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值