一、前言
一个object的属性允许其他object监督和改变他的状态。但是在一个设计良好的面向对象程序中,直接访问一个object的内部状态是不可能的。相反,存取器(getter setter)方法是一个抽象相互作用object的底层数据。
通过访问器方法与属性进行交互
@property指令的目标是通过自动的创建这些存取器方法使创建和配置属性变得更加简单。它允许你在语义级别上指定公有属性的行为。而且它比较关注你的详细实现。
这个模型调查各种各样的属性,这些属性可以让你修改getter和setter行为。其中的一些属性确定是如何处理内存的,所以这个模型也服务于在Objective-C中对内存管理的实际的介绍。
二、@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; //Xcode 4.4以上可选 @end
编译器会为running属性创建一个getter和setter方法。默认的命名习惯是用属性自己作为getter,加上前缀set作为setter方法,并且在前面加下划线作为实例变量,就像下面这样:
-(BOOL)running { return _running; } -(void)setRunning:(BOOL)running { _running = running; } 当用
@property直接生成属性,你可以直接调用这些方法,就像这些方法就是包含在该类的interface和实现文件中。你也可以在.m中重写他们,但是这样会使得@synthesize指令强制。然而,你应该很少需要传统的存取器尽管@property属性供这样做在抽象的级别。
属性访问可以是用类实例后加.访问。所以看下面的代码:
Car *honda = [[Car alloc] init]; honda.running = YES; NSLog(@"%d",honda.running);
当执行honda.running时,也就是调用setRunning:方法。
当给他分配值并且运行时,就是调用getter方法。
为了改变这种存取器方式,我们可以在@properry后加括号指定,下面就是介绍可用的属性。
1、The getter= and setter= Attributes
如果我们不喜欢@property默认的命名方式,我们可以通过The getter= and setter= Attributes 来改变存取器方法名。最常用的就是对Boolean属性使用这个。可以把getter把惯例的改成is,例如:
@property(getter=isRunning) BOOL running;
现在生成存储器叫做isRunning和setRunning.而标注公共性质还是叫做running。下面是我们应该怎么用逗号使用它。
Car *honda = [[Car alloc] init]; honda.running = YES; NSLog(@"%d",honda.running); NSLog(@"%d",[honda isRunning]);
这些是唯一的属性,他们都是boolean标记。
2、readonly属性
readonly属性是一个很方便的方法让你的属性只读。这样会省略setter方法,并且防止作恶通过.调用,但是getter不受影响。例如,我们修改running的属性为readonly,注:我们可以制定多个属性,然后用“,”分开:
#import <Foundation/Foundation.h> @interface Car : NSObject @property(getter=isRunning,readonly) BOOL running; -(void)startEngine; -(void)stopEngine; @end
不是让其他object改变running的属性,我们将会设置两个方法去访问。这两个方法的而实现如下:
-(void)startEngine { _running = YES; } -(void)stopEngine { _running = NO; }
要记得,@property还为我们生成了一个实例变量,这就是我们为什么可以访问_running在没有声明的条件下(我们也可以直接使用self.running因为这个属性是只读的)。让我们来运行下列代码测试:
Car *honda = [[Car alloc] init]; // honda.running = YES; NSLog(@"%d",honda.running); honda.running = NO;
我们会发现最后一句出错,因为它是只读属性,无法修改。
到这个地方,我们可以很方便快捷地让我们避免书写样板的getter和setter方法。而对remaining属性,这不是一个好的情况。他们也只适用于属性存储OC对象(相当于C数据类型)。
3、nonatomic属性
原子性(Atomicity)的作用是属性在线程的环境中怎么行为。当你不仅仅有一个线程, 那么getter和setter可能会在同一时间去调用,这就意味着getter/setter可能会被另一个方法打扰,很有可能造成数据错误。
原子的属性会封锁object,防止这种情况发生,确保get或者set操作的操作对象是完整的。没有被损坏。然而,这仅仅是一个线程安全的方面,我们必须要理解这一点。使用原子性并不能确保我们的代码就是线程安全的。
属性用@property声明默认是原子性的,这会导致一些花销,因此,如果你不是处在多线程环境(或者你正实现你自己的线程安全),你会用notatomic属性重写这个行为,就像下边:
@property (nonatomic) NSString *model; //设置非原子性
当使用原子性属性时,会有一个小的而且比较实际的警告。针对原子属性的属性访问器必须要么是生成的,要么是用户自定义的,只有传统非原子性的属性会让你混合搭配合成存储方法。你可以看一下,如果移去nonatomic从上边的代码,然后再Car.m添加传统的getter。
会产生一个警告:Setter和getter必须被合成,或者用户自定义,或者属性必须是nonatomic
4、内存管理
在面向对象语言中,objects存在于计算机内存中,尤其在移动设备中,内存这是一个很缺乏的资源。内存管理系统以一个高效的管理方式创建和破坏object,目标就是确保程序不占用超出它所需要的空间。
许多语言都是通过垃圾回收去完成的,但是OC用的是一个更加高效替代品,就是Object ownership(目标拥有者)。当你开始与对象交互时,你会告诉对象自己,这就意味着它确保只要你再使用对象,它就会存在.当你不再使用的时候,你放弃所有权,如果对象没有其他的所有者,操作系统会销毁这个对象,然后释放底层内存资源。
随着自动引用计数(ARC)出现,编译器自动管理你所有的对象,大多数情况下,意味着你从来不必担心内存管理系统是怎么工作的,但是,你必须明白strong,weak和copy属性,因为他们告诉编译器对象应该有什么关系。
5、Strong属性
无论对象被指定为什么属性,强壮的属性可以创建拥有关系,这对所有对象属性来说是一种内隐行为,它默认是安全的,因为只要它被指定为strong属性,它就会却倒对对象的值存在。
通过下面的例子,让我们看一下它是怎么工作的。
@interface Person : NSObject @property(nonatomic)NSString *name; @end
它的实现如下,它@property产生的使用默认的存储方法,它也重写了NSObject的描述方法,返回一个代表该对象的字符串。
#import "Person.h" @implementation Person -(NSString *)description { return self.name; } @end
然后,让我们添加Person属性给车,改变Car.h如下:
#import <Foundation/Foundation.h> #import "Person.h" @interface Car : NSObject @property (nonatomic) NSString *model; @property(nonatomic,strong) Person *driver; @end
然后考虑下边的代码:
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);
只要driver是一个strong关联,honda对象就会持有john,这确保只要honda需要它,它就会有效。
6、weak属性
大多数情况下,强属性可以直观知道你想要什么对象属性,强引用会暴漏一个问题,例如,我们需要从driver引用他正在开的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类的前声明,就像它告诉编译器,“相信我,Car类是存在的,所有不要想着去立刻找到它”。我们不用#import这样做,因为Car也导入了Person.h,那样我们会陷入无尽的导入循环(编译器不喜欢无穷的循环)。
然后,添加下面的代码,在honda、dirvier分配后:
Person *john = [[Person alloc] init]; john.name = @"John"; Car *honda = [[Car alloc] init]; honda.model = @"Honda Civic"; honda.driver = john; john.car = honda; //添加这行 NSLog(@"%@ is driving the %@",honda.driver,honda.model);
这样我们现在有一个现象,就是john拥有honda,honda拥有john。这就意味着他们相互拥有,所以尽管他们不再有用,内存管理系统也不能够释放他们。
这叫做 retain cycle(保持循环),是一种内存泄露的形式,内存泄露是很不好的。幸运的是,要想解决这个办法很简单,只需要告诉其中一个属性维持一个weak属性引用另一个对象。在Person.h中,改变car的声明:
@property(nonatomic,weak)Car *car;
这种weak(弱)属性会给car创建一个非拥有关系。它允许john有一个honda的引用,同时避免了保持循环。但是,还有一个可能性就是honda会被销毁,而这个时候john正在引用honda,如果这种情况发生,weak属性会很方便地设置car为nil,去避免悬挂指针。
一个在Person中弱引用car
一个公用的使用weak属性例子就是父亲-孩子数据结构,根据约定,父对象应该保持一个强引用对子对象,子对象应该存储一个弱引用父对象。弱引用在代理设计模式中是内在的,也就是自带的。
重点是两个对象不恩给你都是强引用彼此,weak属性让保持一个不通过创建保持循环循环的关系变成可能。
7、Copy属性
它是strong的替代品,不是保持拥有一个存在的对象,而是创建一个引用,无论你指定什么属性,都会持有这个拥有。只有符合NSCopying protocol的对象才能使用这个属性。
代表值的属性(相对于链接或关系)是一个使用copy的不错选择。例如,开发者通常复制字符串属性,而不是强引用它们:
//Car.h
@property (nonatomic,copy) NSString *model;
现在Car将会存储一个全新的实力,不管我们指定的model值,如果你处理可变的值,它会有一个福利,就是冻结对象无论什么时候有值当它被指定时。演示如下:
Car *honda = [[Car alloc] init]; honda.model = @"Honda Civic"; NSMutableString *model = [NSMutableString stringWithString:@"Honda Civic"]; honda.model = model; NSLog(@"%@",honda.model); [model setString:@"Nissa Versa"]; NSLog(@"%@",honda.model); //输出结果还是Honda Civic
NSMutableString 是NSString的一个子类,它可以在自身的基础上修改它的值。如果model属性不被声明为copy属性,我们将会看到在最后一个NSLog将会输出Nissa Versa
8、其他属性
上面的的这些@property性质我们需要使用在如今的OC应用中,但是下面的一些属性我们可能会在老得类库或者文档中偶尔遇到。
retain属性是手动释放保留版本的strong,它有准确一样的影响:为指定的values声称所有权.ARC环境中不能使用。
它类似与weak,但是如果引用对象销毁,不自动设置nil。使用该属性的唯一原因就是:去让你的类兼容不支持weak属性的代码。
当给一个这个属性指定一个新的value,assign属性不执行任何类型的内存管理调用。这个一个原始数据类型的默认行为,它的应用是在iOS5之前去实现弱引用,就像retain,现在的application你不应该明确的使用。
总结:
这个模块讲解了所有的@property选择属性,下面是总结属性:
参考自: http://rypress.com/tutorials/cocoa/