此文成于 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
- 定义不带属性的实例变量
属性像是类公开的封装的接口
如果某些封装的数据不希望公开,可以使用不带属性的实例变量.
可以使用如下方式声明:
- 在类声明中加一对花括号并在其中声明实例变量
@interface Person:NSObject{
NSString *_myNonPropertyInstanceVariable;
}
- 在类实现中加一个花括号区域,并在其中声明:
@implementation Person{
NSString *_anotherCustomInstanceVariable;
}
- 创建类扩展并在其中声明:
类扩展有一点类似于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
属性就适合实现为一个不带实例变量的属性。
- 声明
@property (readonly) NSString *fullName;
- 实现 以一个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
的属性你自己实现了
getter
和setter
方法,或者对于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 的对象是在堆上动态分配的,
这意味着需要一个指针来跟踪对象的地址。跟标量不一样,并不到通过一个指针变量的范围来决定一个对象的生命周期。只要有其他对象需要它,它就在内存中存活。
因此,相对于考虑单独的考虑每一个对象的生命周期,更应该思考他们之间的关系。
例如对于两个字符串属性 firstName
和 lastName
它们属于 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
这样就不会造成一个对象不修改了某一个属性,同时也影响了另一个属性。
值得注意的是:
声明为
copy
的属性的对象需要实现NSCopying
协议。在初始化方法(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 可见的。引入了 IBOutlet
及 IBAction
宏
在 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 方法的命名约定
- 返回值是
IBAction
- 方法名使用主动词,描述要执行的操作。
- 要求
sender
的参数类型为id
- 可选的参数类型:
UIEvent *
命名为withEvent:
- 返回值是
IBOutlet
IBOutlet 修饰现在还是必须的,
- 对于有 IBOutlet 修饰的也一并,properti 优于 ivar
- 总是将 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
值得注意的几点:
- 它的顺序是没有保证的
- 无论如何,
IBOutletCollection
对应生成的是 NSArray.
由于数组字面量的出现,其变得不再那么有用了。
常见问题
- [※※]@synthesize和@dynamic分别有什么作用?
参考: @synthesize vs @dynamic, what are the differences?