您的位置:首页 > 其它

5.属性

2015-11-04 14:44 351 查看

原文:http://rypress.com/tutorials/objective-c/properties

属性

对象的属性可以使得其他类与之传递信息。但是,在优秀的面向对象设计中,要求一个对象的属性是不能直接被访问的。如果要操作属性可以通过最基本的getters和setters方法去操作。



Interacting with a property via accessor methods

@property注解可以使属性自动产生get和set方法。这样你就不需要在额外去创建他们。

在本章节中将介绍多个参数设定让你可以设定属性在get和set方法上的行为。某些参数设定可以设置属性的内存管理方式,更多的讨论内容你可以访问Memory Management章节了解更多。

@property注解

首先来看一下
@property直接的一些基本作用。请参考如下的Car类的接口和对应的实现。


// Car.h
#import <Foundation/Foundation.h>

@interface Car : NSObject

@property BOOL running;

@end
// Car.m
#import "Car.h"

@implementation Car

@synthesize running = _running;    // Optional for Xcode 4.4+

@end


编译器将自动地为running属性生成get和set方法。默认情况下属性名称即为get方法(xf-xrh-xf译注:就是get方法名不带有get前缀的意思),然后set方法即为以set为前缀加上属性名的语法,就像这样:

- (BOOL)running {
return _running;
}
- (void)setRunning:(BOOL)newValue {
_running = newValue;
}


使用@property标注声明属性之后,如果你将声明文件引入你的类接口文件和实现文件的话你就能使用生成get和set方法去访问属性了。你也可以在Car.m中重写这些方法,但是我们很少这么做。

当使用点操作符号获取属性的时候内部其实是将其转化成对应的方法的。所以下面的
honda.running = YES
语句其实是调用了setRunning:方法,而honda.running被用来读取属性值的时候其实是调用了内部的running方法。

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
Car *honda = [[Car alloc] init];
honda.running = YES;                // [honda setRunning:YES]
NSLog(@"%d", honda.running);        // [honda running]
}
return 0;
}


如果要更改属性的get和set方法的话,你可以修改属性参数(括号内)。接下来的内容中我们会介绍一些常用的属性参数使用方法。

getter= and setter= 属性参数

如果你不喜欢使用@property生成的默认get和set方法,你可以使用
getter=
setter=属性参数进行对get/set方法名称的更改。在众多有修改需求的属性中布尔类型的属性是最常见的,它的get方法经常需要被更名为is为前缀。我们试着在Car.h进行修改吧。


@property (getter=isRunning) BOOL running;


现在此属性自动生成的访问方法为isRunning和setRunning。注意我们这并不应该属性的名称,当你使用点操作符号去获取属性的时候如同下面代码所示:

Car *honda = [[Car alloc] init];
honda.running = YES;                // [honda setRunning:YES]
NSLog(@"%d", honda.running);        // [honda isRunning]
NSLog(@"%d", [honda running]);      // Error: method no longer exists


在属性参数设定中只有针对于get/set改名的设定有参数,其他修改都布尔型的标志位修改。

只读属性参数

readonly参数的使用可以使得某个属性方便的会的只读设定。它内部实现其实是组织set方法的自动生成并阻止利用点操作符赋值,但是get方法依然生效。下面我们举一个例子,让我们按照下面代码片段修改Car.h代码。注意一点,在为属性设定多个参数的时候可以使用都好相隔。

#import <Foundation/Foundation.h>

@interface Car : NSObject

@property (getter=isRunning, readonly) BOOL running;

- (void)startEngine;
- (void)stopEngine;

@end


为了防止其他对象直接修改running属性,我们在接口中定义了startEngine和stopEngine方法。然偶对应地在实现方法如下代码片段所示:

// Car.m
#import "Car.h"

@implementation Car

- (void)startEngine {
_running = YES;
}
- (void)stopEngine {
_running = NO;
}

@end


记住
@property同样给我们生成了一个变量,你可以使用_running但是你却并没有声明过他们(我们不能使用self.running因为他是只读的)。让我们来通过下面代码进行测试吧。


Car *honda = [[Car alloc] init];
[honda startEngine];
NSLog(@"Running: %d", honda.running);
honda.running = NO;                      // Error: read-only property


非原子属性参数

属性的原子操作一般在多线程的场景下使用。当你有超过一个线程的时候,有可能对于属性的set和get同时操作,这样救护导致属性值的混乱。

原子属性可以防止这种情况的产生,使得get和set前后执行保证数据的有效性。然而,请记住这是线程安全的一个方面而已,如果说你自身代码保证了线程内圈那么原子属性参数就不需要了。

使用@property声明的属性自动就是原子化的,所以如果线程安全的前提在会造成性能的下降。所以如果你不在多线程环境下或者说你保证了线程安全的情况下,你可能会将属性参数修改为非原子化。就像这样:

@property (nonatomic) NSString *model;


There is also a small, practical caveat with atomic properties. Accessors for atomic properties must both be either generated or user-defined. Only non-atomic properties let you mix-and-match synthesized accessors with custom ones. You can see this by removing
nonatomic
from the above code and adding a custom getter in
Car.m
.

[xf-xrh-xf译注:以上未翻译,不懂]

内存管理

在任何OOP语言中,对象都是放入内存的,但当为手持设备时候,内存资源是非常宝贵的,所以对于内存的管理就非常重要。内存管理的目标就是让程序不要占用比实际需要的内存还要多的内存。

很多语言通过垃圾回收来解决这个问题,但是OC使用了更加高效的方式-对象关系。当你主动和某个对象交互的时候,你就是这个对象的拥有者。换句话说这个对象在你调用它期间会一直存在。当你完成调用后,你释放这种关系,如果此时对象没有其他拥有者那么我们就可以销毁这个对象并释放这个对象占用的内存。



Destroying an object with no owners

随着 Automatic Reference Counting的出现,编译器可以自动的管理所有的对象的所有者关系。很大程度上说,这样就以为着开发者就不用太关心内存管理的实际工作了。但是,你也必须对@property的strong,weak和copy属性参数要有深入理解,因为这些参数是内存管理方式。

strong 属性参数

strong属性参数对某个对象赋值给对应属性并建立了拥有关系。这种关系是非常牢固的,它保证了该对象的始终存在。

让我们创建一个叫做Person的类去解释此参数的原理。

// Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic) NSString *name;

@end


实现代码如下。这个类采用了默认的get/set方法。它重写了NSObject的description方法来返回当前对象的详细信息。

// Person.m
#import "Person.h"

@implementation Person

- (NSString *)description {
return self.name;
}

@end


接下来我们将一个Person类型声明进Car类中。修改Car.h文件如下。

// Car.h
#import <Foundation/Foundation.h>
#import "Person.h"

@interface Car : NSObject

@property (nonatomic) NSString *model;
@property (nonatomic, strong) Person *driver;

@end


请思考一下如下的main代码:

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"
#import "Person.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *john = [[Person alloc] init];
john.name = @"John";

Car *honda = [[Car alloc] init];
honda.model = @"Honda Civic";
honda.driver = john;

NSLog(@"%@ is driving the %@", honda.driver, honda.model);
}
return 0;
}


既然driver属性被声明为strong类型,那么honda对象就拥有了john。这保证了只要honda存在则john必然存在。

weak 属性参数

大多数情况下,strong属性参数让人感觉很直观,然而strong属性参数也可能会带来一些问题,例如,我们需要在Person对象中要引用car对象。首先,让我们在Person中增加Car属性:

// Person.h
#import <Foundation/Foundation.h>

@class Car;

@interface Person : NSObject

@property (nonatomic) NSString *name;
@property (nonatomic, strong) Car *car;

@end


@class Car注解告诉编译器“信任我,Car类是存在的,但是现在不要去寻找它”。使用这个注解的原因为如果我们在此#import 来引用的话由于Car.h中也引用了Person.h文件这样就导致了无限引用问题。(编译器可不喜欢无限引用)

接下来,在main中将Car对象赋值给Person对象中的属性。

honda.driver = john;
john.car = honda;       // Add this line


现在honda拥有了john对象,john对象拥有了honda对象,这就意味着内存管理系统将无法释放这两个对象。



A retain cycle between the
Car
and
Person
classes

这种情况我们称之为关系保持环,是一种内存泄露,内存泄露在手持设备上的影响尤其巨大。事实上这种情况非常容易解决-只要告诉告诉对象去以一种弱引用的方式引用对方即可。在Person.h中将car的声明变为:

@property (nonatomic, weak) Car *car;


weak属性参数和car指向的对象建立了一种弱拥有的关系。以此来消除刚才说的关系保持环。但是这样也意味着honda对象被销毁了但是john对象依然保持着对她的引用。不过weak会将car变量自动的设置为nil来避免野指针的产生。



A weak reference from the
Person
class to
Car


一个适用于weak属性参数的场景是parnt-child数据结构场景。按照惯例,父对象应该stong引用孩子对象,而孩子对象应该weak引用父对象。weak引用也经常被用在委派的设计模式中。

关键点在于两个对象不能相互的stong引用对方,weak属性参数使得循环引用得意实现但也破除了强引用关系环的产生。

copy 属性参数

copy属性参数可以说是strong属性参数的一种很好替代。它不像strong属性参数去同一个存在的对象建立关系,而是同引用对象的一个拷贝建立关系。

当某个属性读取某对象而并非像去改变某对象的时候可以使用copy属性参数。例如,开发者经常在NSString属性上使用copy:

// Car.h
@property (nonatomic, copy) NSString *model;


现在,Car对象内部就存储了一个拷贝的NSString对象。无论被拷贝的对象如何改变,都不会影响到则个拷贝对象,反之也一样,这一点可以如下代码证明:

// main.m
#import <Foundation/Foundation.h>
#import "Car.h"

int main(int argc, const char * argv[]) {
@autoreleasepool {
Car *honda = [[Car alloc] init];
NSMutableString *model = [NSMutableString stringWithString:@"Honda Civic"];
honda.model = model;

NSLog(@"%@", honda.model);
[model setString:@"Nissa Versa"];
NSLog(@"%@", honda.model);            // Still "Honda Civic"
}
return 0;
}


NSMutableString是NSString类的子类,如果model属性没有copy的参数,那么当原始的NSMutableString对象改变的时候,引用它的属性值也会改变。


其他属性参数

上述的
@property属性参数你会在新的OC应用(IOS5+)中中遇到,但是也有一些老的版本中存在其他一些属性参数。


retain 属性参数

retain属性参数应该说是一个Manual Retain Release版本的strong属性参数,拥有同样的效果:声明对赋值对象拥有强引用。在ARC内存环境中你不应该使用它。

unsafe_unretained 属性参数

unsafe_unretained属性参数很像weak属性参数,但是它不会在被也引用对象销毁的时候自动的将属性设置为nil。也一样,在新版本的OC框架中避免使用到它。

assign 属性参数

assign属性参数不会触发任何的内存管理动作。同样现在你也不应该使用它

总结

在本章节中介绍了
@property的属性参数,我们希望你能够活学活用他们。记住这些参数的目的是让编译器知道如何去自动的管理内存。总结如下:


AttributeDescription
getter=
Use a custom name for the getter method.
setter=
Use a custom name for the setter method.
readonly
Don’t synthesize a setter method.
nonatomic
Don’t guarantee the integrity of accessors in a multi-threaded environment. This is more efficient than the default atomic behavior.
strong
Create an owning relationship between the property and the assigned value. This is the default for object properties.
weak
Create a non-owning relationship between the property and the assigned value. Use this to prevent retain cycles.
copy
Create a copy of the assigned value instead of referencing the existing instance.
现在关于属性部分我们介绍完了,后面介绍OC类的另外一半:方法。我们将深入探索。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: