Objective-C Properties

来源:互联网 发布:java开发网上商城 编辑:程序博客网 时间:2024/06/13 11:47

Properties

一个对象的属性可以使其他对象检查或改变其状态. 但是, 对于一个好的面向对象程序设计而言, 外部不可能直接访问对象内部的状态. 相反, 存取方法(accessor methods(getters & setters))被用作与对象底层数据交互的抽象概念.

通过存取方器同属性交互

@property指令的目的是使通过自动生成这些存取器方法来创建及配置属性变得容易. 它允许你在语义层面指定公共属性的行为, 同时顾及了实现部分的细节.

The @property Directive

首先来看看在使用@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; @end

编译器生成了为属性running生成了一个访问器和设置器, 默认的命名习惯是使用属性本身作为访问器, 加前缀set作为设置器, 在属性前加下划线作为实力变量:

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

在使用@property声明属性之后, 就可以像在类的接口和实现文件中调用这些方法一样调用它们. 同时也可以在Car.m中重写它们以支持自定义的getter/setters, 但是这样做会使@synthesize成为强制性的. 然而, 很少的自定义存取器, 因为@property属性语义在抽象层面让你这样做.

通过点语法访问属性时, 在幕后会转换到上述存取器方法, 因此, 当分配值给属性以及在running方法读取值的时候, 下面honda.running代码真正意义上是调用了setRunning:方法.

// 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;}

为了改变生成的访问器的行为, 你可以在@property之后括号内指定属性语义.

The getter= and setter= Attributes

如果你不喜欢@property默认的命名习惯, 可以使用getter= 和 setter=语义更改getter/setter方法名. 常用的是为Boolean属性更改名字, 按常规其getter前加前缀is.

@property (getter=isRunning) BOOL running;

现在, 合成的访问器称作isRunningsetRunning. 注意, 公共属性仍然为running, 而且必须使用点语法:

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

The readonly Attribute

readonly是仅使属性具有只读的语义. 它删掉了setter方法阻止通过点语法给属性赋值, 但是getter方法未受影响. 如下, 改变Car的接口部分. 注意如何通过逗号(,)分隔来说明多重语义.

#import <Foundation/Foundation.h>@interface Car : NSObject@property (getter=isRunning, readonly) BOOL running;- (void)startEngine;- (void)stopEngine;@end

为了不让其他对象改变running属性, 我们在内部通过startEnginestopEngine方法来设置. 对应的实现如下:

// Car.m#import "Car.h"@implementation Car- (void)startEngine {    _running = YES;}- (void)stopEngine {    _running = NO;}@end

@property同时为我们合成了一个实例变量, 这就是为什么我们可以访问_running而不用在任何一处声明它(此处不能使用self.running, 由于属性是只读的). 在main.m函数中添加以下代码来测试这个新的Car类:

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

直到此时, 属性做到了真正的便捷简写, 避免书写getter和setter方法的样板代码. 这不是其他剩余语义的例子, 它显著地改变了属性的行为. 它们仅适用于存储Objective-C对象属性(相对于C原始的数据类型).

The nonatomic Attribute

原子性控制属性在线程环境下的行为. 如果你有多条线程时, setter和getter有可能会被同时调用. 这意味着getter/setter会被另外一个操作给打断, 很有可能产生乱码.

原子性属性锁定了底层对象以阻止这种可能性发生, 保证get或set操作具有一个完整地值. 但是, 理解这个只是线程安全使用的一方面是重要的, 原子性不在需要意思是你的代码是线程安全的.

@property声明的属性默认的是原子性的, 这导致一些. 如果你没有在多线程环境, 那么使用nonatomic语义来重写这种行为.

@property (nonatomic) NSString *model;

原子性属性同样也有小而实用的警告. 原子性属性访问器必须同时具备合成或用户自定义性.

Memory Management

在任何一个面向对象编程(OOP)语言中, 对象都在计算机内存当中-特别是对于手机设备来说, 内存是相当稀缺的资源. 内存管理系统就是为了确保程序不会占有多余的空间, 它们必须以高效的方式来创建和销毁对象.

大多语言通过垃圾回收(garbage collection)机制来完成该过程, 但是Objective-C使用了更有效地替代方案, 称之为object ownership. 当你和一个对象进行交互的时候, 你拥有了那个对象, 这意味着保证在你对象交互的期间它是一直存在的. 当你与其交互完毕, 你放弃持有该对象并且-假如该对象没有其他的持有者-操作系统销毁对象并释放底层内存.

销毁没有拥有者的对象

随着自动引用计数的到来, 编译器自动地管理了所有对象持有关系. 大部分来说, 这意味着你再也不必担心内存管理系统究竟是如何运行的, 但是你必须理解@propertystrong, weakcopy语义, 因为他们告诉编译器对象所被持有的关系.

The strong Attribute

strong语义创建了一种拥有关系, 对于不论哪个对象被分配给其属性. 这是一种对所有对象来说的隐式行为, 这是一种默认的安全行为, 由于它保证其值和属性的生命周期一样.

让我们通过创建另外一个类Person看看是如何工作的. 该类接口仅声明了name:

// Person.h#import <Foundation/Foundation.h>@interface Person : NSObject@property (nonatomic) NSString *name;@end;

实现部分如下, 使用了由@property默认生成的访问器. 同时重写了NSObject的description方法, 返回字符串代替对象.

// Person.h#import "Person.h"@implementation Person- (NSString *)description {    return self.name;}@end

接下来, 给Car类添加一个Person属性. 如下,

// Car.h#import <Foundation/Foundation.h>#import "Person.h"@interface Car : NSObject@property (nonatomic) NSString *model;@property (nonatomic, strong) Person *driver;@end

main.m函数中:

// 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需要时它是可用的.

The weak Attribute

大多时候, strong语义是凭直觉来确认你想要的对象属性. 但是, 强引用也有问题, 比如如果我们需要一个从driverCar对象的引用, 首先为Person.h添加一个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类的向前声明(forward declaration). 它告诉编译器, “相信我, Car类是存在的, 因此没必要此刻就去找到它.” 我们必须用此来代替通常使用的#import因为在Person.m中也要引入Car, 不然就会产生循环引入.

接下来, 在main.m中, honda.driver后添加如下语句:

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

现在, hondajohn具有持有关系, 而且johnhonda也有同样关系. 这意味着两个对象之间永远拥有对方, 那么内存管理系统将在其不使用的情况下也无法进行销毁.

Car类和Person类之间循环引用

这种现象被称为循环引用(retain cycle), 这是内存泄露的一种形式. 所幸的是, 修复这种问题非常简单, 只需要将一个对象对另一个对象保持若引用(weak reference)即可. Person.h中, 改变car的声明如下:

@property (nonatomic, weak) Car *car;

weak语义对car创建的以非拥有的关系. 这允许johnhonda有引用而避免了循环引用. 但同时, 这也意味着有可能hondajohn对其还有引用存在时被销毁. 如果发生这种情况, weak语义会很方便的将car置为nil而避免野指针.

Person对Car的弱引用

weak语义常用的用法是用于父-子数据结构. 习惯上, 父对象应该对子对象持有强引用, 而子对象则应该对父对象持有弱引用. 弱引用也是代理设计模式(delegate design pattern)固有的部分.

解决的办法就是两个对象之间不要全是强引用. weak语义使得保持有周期性的关系(cyclical relationship)同时避免了循环引用成为了可能.

The copy Attribute

copy语义是strong的替代品. 与获取存在对象的持有权不同, copy创建了一个属性的副本, 然后对副本持有. 仅有遵从NSCopying protocol的对象才能使用该语义.

属性代表值(相对连接或关系)非常易于复制. 比如, 开发者通常复制NSString属性而非强引用:

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

此时, Car会存储一个我们分配给model的新的实例. 如果你使用了可变的值, 这会有额外的好处, 可以在当对象分配了可变值的时候被, 不论再次分配任何值都不会变.

// 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;}

NSMutableStringNSString的子类, 它能够就地被编辑. 如果model属性没有创建初始实例的副本, 我们会在第二个输出函数NSLog()中看到改变之后的字符串(Nissan Versa).

Other Attributes

以上@property语义是新版本Objective-C应用(iOS 5+)所必须的, 但是有一些其他的语义你可能会在早起库或文件中发现.

The retain Attribute

retain语义是手动内存管理(Manual Retain Release)版本中的strong, 并具有相同的影响: 声明了分配值的所有权. 在自动引用计数中不会用到它.

The unsafe_unretained Attribute

具有unsafe_unretained语义的属性行为类似于weak语义的属性, 但是如果饮用对象被销毁它们不会自动置为nil. 你必须使用unsafe_unretained的原因是使你的类和代码兼容, 二代码不支持weak属性.

The assign Attribute

当分配给属性新值时, assign语义不会执行任何一种内存管理调用. 这是早期数据类型的默认行为, 在iOS 5 之前它是用于实现弱引用的. 比如retain, 不必在现代应用中明确使用它.

Summary

本节展现了@property全部可用的语义, 我们希望你对修改合成访问器方法行为感到相对舒适. 所有这些语义的目标是帮助你聚焦什么样的数据需要记录, 允许编译器自动地决定如何展示.

Attribute Description getter= 为getter方法使用自定义的名字 setter= 为setter方法使用自定义的名字 readonly 不用合成setter方法 nonatomic 不保证在多线程环境下访问器的完整性. 这比默认的原子性 strong 在属性和被分配的值之间创建一个拥有关系. 这是对象属性默认的 weak 在属性和被分配的值之间创建一个非拥有关系. 使用该语义阻止循环引用 copy 创建一个被分配值的副本来代替引用

本文翻译自:http://rypress.com/tutorials/objective-c/properties

0 0
原创粉丝点击