iOS----属性

 声明属性

     Objective-C声明的属性特性提供了一种简单的方法来声明和实现对象的访问器方法。

     概述

     您通常通过一对访问器(getter/setter)方法访问对象的属性(从属性和关系的角度来看)。通过使用访问器方法,您可以遵循封装的原则(请参阅Objective-C面向对象编程中的抽象机制)。您可以严格控制getter/setter对的行为和底层状态管理,而API的客户端与实现更改保持隔离。

     虽然使用访问器方法有很大的优点,但是编写访问器方法是一个冗长的过程。此外,可能对API使用者很重要的属性的某些方面被忽略了——例如访问器方法是否是线程安全的,或者在设置时是否复制新值。


     声明的属性通过提供以下特性来解决这些问题:

     1.属性声明提供了访问器方法行为的明确规范。

     2.编译器可以根据声明中提供的规范为您合成访问器方法。

     3.属性在语法上表示为标识符,并且具有作用域,因此编译器可以检测未声明属性的使用。


     属性声明及实施

     声明的属性有两部分:声明和实现。

     属性声明

     属性声明以关键字@property开头。属性可以出现在类的@interface块中方法声明列表的任何位置。@property还可以出现在协议或类别的声明中。

     @property (attributes) type name;


     @property指令声明了一个属性。一个可选的圆括号属性集提供了关于属性的存储语义和其他行为的附加细节,请参阅属性声明属性以获取可能的值。与任何其他Objective-C类型一样,每个属性都有一个类型规范和名称。


     清单4-1声明了一个简单的属性在:

     @interface MyClass : NSObject

     @property float value;

     @end


     您可以将属性声明看作等同于声明两个访问器方法。因此

     @property float value;

     等价于:

     - (float)value;

     - (void)setValue:(float)newValue;


     属性声明的属性

     您可以使用form @property(attribute [, attribute2,…])来装饰属性。与方法一样,属性的作用域限定在其封闭的接口声明上。对于使用逗号分隔的变量名列表的属性声明,属性属性适用于所有已命名的属性。


     如果您使用@synthesize指令告诉编译器创建访问器方法(请参阅属性实现指令),它生成的代码将与关键字给出的规范匹配。如果您自己实现访问器方法,您应该确保它与规范匹配(例如,如果指定copy,您必须确保在setter方法中复制输入值)。



     访问器方法名称

     与属性关联的getter和setter方法的默认名称分别是propertyName和setPropertyName:—例如,给定属性“foo”,访问器将是foo和setFoo:。以下属性允许您指定自定义名称。它们都是可选的,并且可以与任何其他属性一起出现(在setter=中除了readonly)。


     getter = getterName

     指定属性的get访问器的名称。getter必须返回与属性类型匹配的类型,并且不带参数。

     setter = setterName

     指定属性的集访问器的名称。setter方法必须接受与属性类型匹配的类型的单个参数,并且必须返回void。

     如果您指定一个属性是只读的,并且还指定一个setter和setter=,您将得到一个编译器警告。

     通常,您应该指定符合键值编码的访问器方法名称——使用getter修饰符的常见原因是遵守布尔值的isPropertyName约定。



     Setter语义

     这些属性指定集合访问器的语义。它们是相互排斥的。

     strong----指定与目标对象具有强(拥有)关系。

     weak--指定与目标对象存在弱(非所有)关系。

     如果目标对象被释放,属性值将自动设置为nil。

     (OS X v10.6和iOS 4不支持弱属性;使用分配。)

     copy----指定对象的副本应用于赋值。

     前面的值发送一个发布消息。

     复制是通过调用复制方法完成的。该属性仅对对象类型有效,对象类型必须实现NSCopying协议。


     assign---指定setter使用简单的赋值。这个属性是默认的。

     对于NSInteger和CGRect等标量类型,可以使用此属性。


     retain---指定赋值时应在对象上调用retain。

     前面的值发送一个发布消息。

     在OS X v10.6及以后版本中,您可以使用__attribute__关键字来指定一个Core Foundation属性应该被当作一个Objective-C对象来进行内存管理:

     @property(retain) __attribute__((NSObject)) CFDictionaryRef myDictionary;


     原子性

     可以使用此属性指定访问器方法不是原子的。

     原子

     指定访问器是非原子的。默认情况下,访问器是原子的。

     属性在默认情况下是原子的,因此合成的访问器提供对多线程环境中的属性的健壮访问——也就是说,无论其他线程并发执行什么,从getter或setter返回的值总是被完全检索或设置。

     如果指定强值、复制或保留值,而不指定非原子值,那么在引用计数环境中,对象属性的合成get访问器使用锁并保留并自动释放返回的值——实现将类似于以下内容:

     [_internal lock]; // 使用对象级锁进行锁定

     id result = [[value retain] autorelease];

     [_internal unlock];

     return result

     如果指定非原子性,则对象属性的合成访问器将直接返回值。


     标记和弃用

     属性支持所有c风格的装饰器。属性可以被弃用,并支持__attribute__样式标记:

     property CGFloat x

     AVAILABLE_MAC_OS_X_VERSION_10_1_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_4;

     @property CGFloat y __attribute__((...));

     如果要指定属性是outlet(参见iOS中的outlet和OS X中的outlet),则使用IBOutlet标识符:

     @property (nonatomic, weak) IBOutlet NSButton *myButton;


    属性实现指令

     您可以在@implementation块中使用@synthesize和@dynamic指令来触发特定的编译器操作。注意,任何给定的@property声明都不需要这两种方法。


     重要提示:如果您没有为特定属性指定@synthesize或@dynamic,则必须为该属性提供getter和setter(对于只读属性,则仅提供getter)方法实现。如果不这样做,编译器将生成一个警告。


     @ synthesize

     使用@synthesize指令告诉编译器,如果不在@implementation块中提供属性的setter和/或getter方法,则编译器应该对它们进行合成。如果没有其他声明,@synthesize指令也会合成适当的实例变量。


     Listing 4-2  Using @synthesize

     @interface MyClass : NSObject

     @property(copy, readwrite) NSString *value;

     @end


     @implementation MyClass

     @synthesize value;

     @end


     您可以使用form property=ivar来指示应该为该属性使用一个特定的实例变量,例如:

     @synthesize firstName, lastName, age=yearsOld;

     这指定应该合成firstName、lastName和age的访问器方法,并且属性年龄由实例变量yearsOld表示。合成方法的其他方面由可选属性决定。

     无论您是否指定实例变量的名称,@synthesize指令都只能使用来自当前类的实例变量,而不能使用超类。

     访问器合成的行为取决于运行时:

     对于遗留运行时,实例变量必须在当前类的@interface块中声明。如果存在与该属性同名的实例变量,且其类型与该属性的类型兼容,则使用它—否则,您将得到一个编译器错误。


     @dynamic

     您可以使用@dynamic关键字告诉编译器,您将通过直接提供方法实现或在运行时使用其他机制(如代码的动态加载或动态方法解析)来实现属性所隐含的API契约。如果编译器找不到合适的实现,它就会压制那些警告。只有当您知道这些方法将在运行时可用时,您才应该使用它。

     清单4-3所示的示例演示了如何使用@dynamic和NSManagedObject的子类。

     @interface MyClass : NSManagedObject

     @property(nonatomic, retain) NSString *value;

     @end


     @implementation MyClass

     @dynamic value;

     @end


     NSManagedObject由Core Data框架提供。托管对象类具有相应的模式,该模式为类定义属性和关系;在运行时,Core Data框架根据需要为这些对象生成访问器方法。因此,您通常会为属性和关系声明属性,但是您不需要自己实现访问器方法,也不应该要求编译器这样做。但是,如果只是声明属性而不提供任何实现,编译器将生成警告。使用@dynamic会抑制警告。


     属性重新声明

     您可以在子类中重新声明属性,但是(除了readonly和readwrite之外)必须在子类中全部重复其属性。在类别或协议中声明的属性也是如此——虽然属性可以在类别或协议中重新声明,但属性的属性必须全部重复。


     如果在一个类中将属性声明为只读,则可以在类扩展、协议或子类中将其重新声明为读写。在类扩展重新声明的情况下,属性在任何@synthesize语句之前重新声明的事实会导致setter被合成。将只读属性重新声明为读/写的能力支持两种常见的实现模式:一个是不可变类的可变子类(NSString、NSArray和NSDictionary都是示例),另一个是具有只读的公共API但内部是私有读写实现的属性。下面的示例展示了如何使用类扩展来提供一个属性,该属性在公共标头中声明为只读,但在私有状态下重新声明为读/写。


     //公共头文件

     @interface MyObject : NSObject

     @property (readonly, copy) NSString *language;

     @end


     //私人实现文件

     @interface MyObject ()

     @property (readwrite, copy) NSString *language;

     @end


     @implementation MyObject

     @synthesize language;

     @end


     核心基础

     正如在属性声明属性中指出的,在OS X v10.6之前,不能为非对象类型指定retain属性。因此,如果您声明了一个类型为CFType的属性,并按照以下示例所示合成访问器

     @interface MyClass : NSObject

     @property(readwrite) CGImageRef myImage;

     @end


     @implementation MyClass

     @synthesize myImage;

     @end


     然后在引用计数的环境中,合成集访问器将新值简单地分配给实例变量(新值不保留,旧值不释放)。对于Core Foundation对象,简单的赋值通常是不正确的;您不应该综合这些方法,而应该自己实现它们。


     子类化的属性

     可以重写只读属性使其可写。例如,您可以使用只读属性value定义一个类MyInteger:

     @interface MyInteger : NSObject

     @property(readonly) NSInteger value;

     @end


     @implementation MyInteger

     @synthesize value;

     @end


     然后可以实现一个子类MyMutableInteger,它重新定义了属性,使其可写:

     @interface MyMutableInteger : MyInteger

     @property(readwrite) NSInteger value;

     @end


     @implementation MyMutableInteger

     @dynamic value;


     - (void)setValue:(NSInteger)newX {

     value = newX;

     }

     @end

     运行时的区别

     一般来说,属性的行为在现代和遗留运行时上都是相同的。有一个关键区别:现代运行时支持实例变量合成,而传统运行时不支持。


     要使@synthesize在遗留运行时中工作,必须提供具有相同名称和兼容类型的实例变量,或者在@synthesize语句中指定另一个现有实例变量。在现代运行时中,如果不提供实例变量,编译器将为您添加一个实例变量。例如,给定以下类声明和实现:

     @interface MyClass : NSObject

     @property float noDeclaredIvar;

     @end


     @implementation MyClass

     @synthesize noDeclaredIvar;

     @end

     遗留运行时的编译器将在@synthesize noDeclaredIvar处生成一个错误;而现代运行时的编译器会添加一个实例变量来表示noDeclaredIvar。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容