Objective-C中与Properties有关的哪些事

此文成于 2015/04/13

属性有两种,一种是基于实例变量,另一种是基于访问器
如果属性是连接其他对象的属性,则需要注意避免强循环引用.(虽然大部分情况下ARC已经替我们处理好大部分情况了.)

属性封装了对象的数据值

属性,就好比是类定义的给使用都的其封装的数据的接口
一个类可以封装一个或者多个数据对象,例如一个NSNumber只封装了一个数值
如下的一个类接口声明:

@interface Person:NSObject
@property NSString *firstName;
@property NSString *lastName;
@end

使用访问器来获取或者设置属性值

NSString *firstName = [somePerson firstName];
[somePerson setFirstName:@”Johnny”];

编译器为自动为属性合成成访问器方法.
合成的方法遵守下面的命名约定:

  • 用于访问属性值的方法(getter 方法)跟属性同名,如名为firstName的属性的getter方法也叫做firstName

  • 用于设置属性值的方法(setter)由以”set”为前缀加首字母大小的属性名组成
    如firstName的setter方法为,setFirstName:

  • 只读属性(readonly)
    如果需要某一个属性是只读的,可以像下面这样声明:

@property (readonly) NSString *fullName;

如果不声明的话,属性默认有一个(readwrite)的特性

  • 自定义属性访问器名称
    例如对于布尔型的属性,我们可能想要用一个iS开始的访问器名称
    可以像下面这样设置
 @property(getter=isFinished) BOOL finished;
  • 点式风格的属性访问方式
    相比上面的消息的方法调用的风格的,获得和设置属性值Objective-C
    还提供如下的点式风格的写法:
NSString *firstName = somePerson.firstName;
somePerson.firstName = @”Johnny”;

其实这只是一个编译器提供的语法糖,实际上还是调用的访问器方法
somePerson.firstName 相当于[somePerson firstName]
somePerson.firstName = @”Johnny”相当于 [somePerson setFirstName:@”Johnny”]

  • 基于实例变量的属性
    默认情况下,一个readwrite属性是基于一个基于一个实例变量的,同样,编译器会帮你自动合成实例变量.
    当对象创建时(使用alloc),就会为实例变量分配相应的内存
    默认情况下,合成的实例变量名是属性名前面加一个下划线如firstName的属性相应的实例变量名为_firstName
    下划线有时也可以方便的跟局部变量区别开来

  • 自定义实例变量名
    可以在类的实现中使用@synthesize的编译指令来指定生成的实例变量名
    如下

@implementation Person
@synthesize firstName = instanceFirstName;
@end

注意如果使用了@synthesize指令,但是没有指定新的实例变量名,
会导致编译器生成一个跟属性名同名的实例变量名,
如下代码

@synthesize firstName;

这样相应的实例变量名会成为firstName而不是默认的_firstName

  • 定义不带属性的实例变量
    属性像是类公开的封装的接口
    如果某些封装的数据不希望公开,可以使用不带属性的实例变量.
    可以使用如下方式声明:
  1. 在类声明中加一对花括号并在其中声明实例变量
@interface Person:NSObject{
    NSString *_myNonPropertyInstanceVariable;
}
  1. 在类实现中加一个花括号区域,并在其中声明:
@implementation Person{
    NSString *_anotherCustomInstanceVariable;
}
  1. 创建类扩展并在其中声明:
    类扩展有一点类似于Category,但是类扩展只适用于你拥有对应类的源代码的情况
    类与类扩展同时编译.
    因为类扩展中声明中的方法必须在原始的类的实现块中实现
    声明类扩展的语法如下(类似一个Category,但是不具名,所以有时也被称之为匿名Category)
@interface ClassName (){
   id _extraCustomInstanceVariable;
}
@property NSObject *extraProperty;
@end

注意到,类扩展也可以声明属性,类扩展一般用来隐藏一些封装的数据
参考 :https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW1
参考中还有一点有意思的是: 可以将在一个框架中可用的方法,放在类扩展中,但是放在一个单独的头文件中,
发布框架时不会将此头文件也发布.

  • 定义不带实例变量的属性
    比如上面提到的 fullName属性就适合实现为一个不带实例变量的属性。
  1. 声明 @property (readonly) NSString *fullName;
  2. 实现 以一个computed property 的方法来实现
- (NSString *) fullName{
 return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
  • 为基于实例变量的属性自定义访问器方法
    在这样的访问器方法中,需要直接使用实例变量,否则就会循环引用的 bug

如下:

- (XYZObject *)someImportantObject{
 if(!_someImportantObject){
   _someImportantObject = [[XYZObject alloc] init];
 }
 return _someImportantObject;
}

这也实现了一种 "lazy accessor"的模式。

  • 不要在初始化方法(init)中使用访问器
    应该直接使用实例变量,因为此时对象初化并没有完成。可能会有意想不到的问题。
    而且属性访问方法如 setter方法会有触发 KVC 的副作用。

  • 编译器自动合成实例变量的说明
    一般编译器会至少合成一个访问方法及实例变量,如果对于 readwrite的属性你自己实现了
    gettersetter方法,或者对于readonly的属性,你实现的getter方法。编译器则不会自动生成实例变量了,如果你还需要生成,可以使用 @synthesize指令:
    @synthesize proper = _property

Property 的线程安全性问题

默认情况下 Objective-C 的属性都是 atomic

@interface XYZObject: NSObject
@property NSObject *implicitAtomicObject;
@property (atomic) NSObject *explicitAtomicObject;
@end

因此上面两个属性声明是的修饰上是等价的。
atomic 也意味着生成的getter,setter也是线程安全的。

如果确定某一个属性是不需要线程安全的保护,可以将其声明为 nonatomic
因为nonaotomic属性是直接访问,所以其速度会更快。

不过需要注意的是:

属性的atomic 并不意味着 对象的线程安全性。

通过从属关系及责任管理对象图

Objective-C 的对象是在堆上动态分配的,
这意味着需要一个指针来跟踪对象的地址。跟标量不一样,并不到通过一个指针变量的范围来决定一个对象的生命周期。只要有其他对象需要它,它就在内存中存活。
因此,相对于考虑单独的考虑每一个对象的生命周期,更应该思考他们之间的关系。
例如对于两个字符串属性 firstNamelastName 它们属于 XYZPerson,
只要XYZPerson对象还存活在内存中,这两个属性也就应该一直存活在内存中。

在 Objective-C 中,只要有一个来自于其他对象的强引用,当 XYZPerson在内存中被释放之后,
firstName,lastName 对应的字符串对象也会立即被释放。

默认情况下,Objective-C 中声明的属性之前的关系都是 强引用 strong 的关系。
有时这会自动布局循环引用的问题。
为此 Objective-C 提供了 弱引用类型 weak

对于变量来说,如果不想一个变量维护一个强引用可以将其声明为 __weak,如下:
NSObject * __weak weekVariable;

为了安全,将所变量所引用的对象被释放之后,其值将设置成为nil
不过下面的__week修饰不合适的:
NSObject * __weak someObject = [[NSObject alloc] init];
因为新创建的对象没有强引用,它被马上释放掉了,someObject马上被设置为nil

__weak 相对的是 __strong 不过我们不需要指定 __strong修饰,因为它是默认启用的。

  • 在使用范围内缓存强引用
    如下:
- (void)someMethod{
   [self.weekProperty doSomething];
   // ...
   [self.weekProperty doSomethingElse];
}

在上面的方法中,无法保存方法执行到后面self.weekProperty还不为空。所以执行到后面可能导致问题。
一个简单的解决方法时,使用一个局部强引用类型变量来在使用范围内对属性引用加强。
如下:

- (void)someMethod{
   NSObject *cachedObject = self.weekProperty;
   
   [cachedProperty doSomething];
   // ...
   [cachedObject doSomethingElse];
}

局部变量的强引用可以保证在此需要此弱引用 属性的方法中,其一直不为被释放。

适用于某一些类的 不安全弱引用。

对于 weak__weak 属性一般为认为是比较安全的,因为在引用对象释放之后,
其属性或者变量会变成 nil 而不是一个危险的野指针。
但是 Cocoa及 Cocoa Touch中的一些类不能使用 weak,及 __weak修饰。
如: NSTextView,NSFont,NSColorSpace等。
这些对象的弱引用可以使用 unsafe_unretained来修饰属性。
使用 __unsafe_unretianed来修饰变量。

属性复制

如果某一个对象需要维护自己的一份属性,可以将其属性声明为
copy 这样就不会造成一个对象不修改了某一个属性,同时也影响了另一个属性。
值得注意的是:

  1. 声明为 copy 的属性的对象需要实现 NSCopying 协议。

  2. 在初始化方法(init)中,应该设置为其对象的一个拷贝,如下所示:

- (instancetype)initWithSomeOriginalString:(NSString*)aString{
   self = [super init];
   if(self){
     _instanceVariableForCopyProperty = [aString copy];
   }
}

小结

@property 的修饰属性主要有:
readonly,readwrite,strong,weak,unsafe_unretained,copy,atomic,nonatomic,getter,setter

上面没有提到两个属性,一个是 assign,一个是retain
这两个属性是在没有ARC时比较常用。但是有ARC一般就不用了。
assign是直接赋值,比较适用于标题,retian在赋值时引用计数会加1.

@property 的其他编译期修饰 IBAction / IBOutlet / IBOutlet Collection

参考: http://nshipster.com/ibaction-iboutlet-iboutletcollection/
对于编程而言,一些指令开始是必须的后来就退化成线索了。
#pragma,method type encoding, 及之前非常必须的 storage classes 随着编译器的越来越强大。
都不再是必须的了。

IB 两个字母是 Interface Builder 的缩写。Xcode 4 之前,Interface Builder 还是独立于 Xcode 而存在的。
在当时这是两个独立的应用时,为了表明哪一部分的代码 是对 Interface Builder 可见的。引入了 IBOutletIBAction
UINibDeclarations.h 中如下表示:

#ifndef IBOutlet
#define IBOutlet
#endif

#ifndef IBAction
#define IBAction void
#endif

#ifndef IBInspectable
#define IBInspectable
#endif

#ifndef IB_DESIGNABLE
#define IB_DESIGNABLE
#endif

不过 Clang 最终对其进行了一定的特殊处理。其编译成了如下的基于 attribute 的 attribute

#define IBOutlet __attribute__((iboutlet))
#define IBAction __attribute__((ibaction))

IBAction

很早之前 IBAction 就不再是必须的了。有如下签名的:-(void){name}:(id)sender 的方法在 outlets 面板就是可见的了。
但是IBAction 作为一种标识 target/action 方法也是一个不错的选择。

  • IBAction 方法的命名约定
    1. 返回值是 IBAction
    2. 方法名使用主动词,描述要执行的操作。
    3. 要求sender的参数类型为 id
    4. 可选的参数类型: UIEvent * 命名为 withEvent:

IBOutlet

IBOutlet 修饰现在还是必须的,

  1. 对于有 IBOutlet 修饰的也一并,properti 优于 ivar
  2. 总是将 IBOutlet 属性声明为 weak,除非实现在有必要才声明为 strong,
    因为一般一个 View 被其父 View 所拥有 ,而 top-level object 一般为 File's Owner 所拥有。
    如果不声明为 weak 有可能造成循环引用的问题。

IBOutletCollection

UINib 它是这样定义的 : #define IBOutletCollection(ClassName)
然后在 Clang 相关代码是这样的:#define IBOutletCollection(ClassName) __attribute__((iboutletcollection(ClassName)))

@property (nonatomic, strong) IBOutletCollection(UIButton) NSArray *buttons;
一般声明为 strong
值得注意的几点:

  1. 它的顺序是没有保证的
  2. 无论如何, IBOutletCollection 对应生成的是 NSArray.

由于数组字面量的出现,其变得不再那么有用了。

常见问题

  1. [※※]@synthesize和@dynamic分别有什么作用?
    参考: @synthesize vs @dynamic, what are the differences?

参考文档

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html#//apple_ref/doc/uid/TP40011210-CH5-SW6

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

推荐阅读更多精彩内容