1. @porperty
简介
Objective-C
的属性 (property) 是通过用@property
定义。例如:
@property (readonly, getter=isBlue) BOOL blue;
属性捕获了对象的状态。它们反映了对象的固有属性 (intrinsic attributes) 以及对象与其他对象之间的关系。属性(property)提供了一种安全、便捷的方式来与这些属性 (attribute) 交互,而不需要手动编写一系列的访问方法,如果需要的话可以自定义getter
和setter
方法来覆盖编译器自动生成的相关方法。
尽量多的使用属性(property)而不是实例变量,因为属性(property)相比实例变量有很多的好处:
- 自动合成
getter
和setter
方法。当声明一个属性 (property) 的时候编译器默认情况下会自动生成相关的getter
和setter
方法 - 更好的声明一组方法。因为访问方法的命名约定,可以很清晰的看出
getter
和setter
的用处。 - 属性(property)关键词能够传递出相关行为的额外信息。属性提供了一些可能会使用的特性来进行声明,包括
assign
,copy
,weak
,strong
,atomic
,nonatomic
,readwrite
,readonly
等。
2. @property
用法
1. 手动定义实例变量
手动定义实例变量,以及手动创建变量的getter
和setter
的声明、实现:
@interface Person : NSObject {
NSString *_name;
NSUInteger _age;
}
//在 .h 文件声明 getter 和 setter 方法.
- (void)setName:(NSString*)name;
- (NSString*)name;
- (void)setAge:(NSUInteger)age;
- (NSUInteger)age;
@end
@implementation Person
//在 .m 文件实现 getter 和 setter 方法。
//getter 和 setter 本质就是符合一定命名规范的实例方法。
- (void)setName:(NSString*)name {
_name = [name copy];
}
- (NSString*)name {
return _name;
}
- (void)setAge:(NSUInteger)age {
_age = age;
}
- (NSUInteger)age {
return _age;
}
@end
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
[p setName:@"XXXX"]; //p.name = @"XXX";
[p setAge:20]; //p.age = 20;
NSLog(@"%@ %ld", [p name], [p age]);
// NSLog(@"%@ %ld", p.name, p.age);
//Objective-C 允许使用点语法来访问方法。
//使用点语法访问的方式本质还是调用了我们手动创建的 setter 和 getter。
return 0;
}
2. 使用@property
声明属性
1. @property
使用@property
声明两个属性name
和age
并自动生成属性的getter
和setter
方法的声明(Xcode4.5之前):
@interface Person : NSObject
@property NSString* name;
@property NSUInteger age;
@end
@implementation Person
- (void)setName:(NSString*)name {
_name = [name copy];
}
- (NSString*)name {
return _name;
}
- (void)setAge:(NSUInteger)age {
_age = age;
}
- (NSUInteger)age {
return _age;
}
@end
2. @synthesize
@synthesize
是一个编译器指令,使用@synthesize
为这两个属性自动生成(编译器默认)名为_name
和_age
的私有实例变量,(在.m文件中生成的)来存储属性,并自动生成属性的getter
和setter
方法的的实现, 也可以自定义getter
和setter
方法来覆盖编译器默认生成的方法,就如同手动创建getter
和setter
一样。
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
@implementation Person
@synthesize name = _name;
@synthesize age = _age;
@end
/*
@synthesize age = _age;
编译器会在编译时会自动生成并使用实例变量(_age)来存储 age 属性, _age 和 age 没什么关系, 只是在上层使用这两个属性的时候可以用 name 和 age 的点语法来访问 getter 和 setter。
如果不想自动生成并使用默认的实例变量来存储 age 属性也可以任意命名,但最好按照官方的命名原则来命名;
@synthesize age = XXXX;
编译器会在编译时会自动生成并使用 XXXX 这个实例变量来存储 age 属性
@synthesize age;
属性后面没有告诉系统将传入的值赋值给谁, 编译器会在编译时会自动生成并使用与属性同名的实例变量来存储 age
*/
3. 使用@property
(增强)声明属性
Xcode4.5之后 使用@property
声明属性,自动做了三件事
- .h : 声明属性的
getter
和setter
方法; - .m : 实现属性
getter
和setter
方法; - .m : 声明私有实例变量(默认:_属性名);
3. @property
修饰符
使用@property
声明两个属性name
和age
并为其设置了一些指示符.
@interface Person : NSObject
@property (nonatomic, copy) NSString* name;
@property (nonatomic, assign) NSUInteger age;
@end
在声明属性的时候一般会带上几个指示符,指示符有(前三种类型常用):
- Atomicity(原子性):
atomic
/nonatomic
- Access(存取特性):
readwrite
/readonly
- Storage(内存管理特性):
strong
/weak
/assign
/copy
/retain
getter = XXX
/setter = XXX
- Nullability:
nullable
/nonnul
/null_unspecified
/null_resettable
1. 原子性
1. atomic
默认属性, 在一定程度上可以保证线程安全,atomic
的作用只是给getter
和setter
加锁。即, 有线程在访问setter
, 其他线程只能等待完成后才能访问。
2. nonatomic
不保证你获得的是有效值,如果像上面所述,读、写两个线程同时访问变量,有可能会给出一个无意义的垃圾值。因为atomic
并不能完全保证程序层面的线程安全,又有额外的性能耗费,所以设置都会使用nonatomic
,这样能够提高访问性能。atomic
与nonatomic
的本质区别其实也就是在setter
方法上的操作不同。
2. 存取特性
3. readwrite
默认属性, 编译器自动生成属性的getter
和setter
。
4. readonly
编译器只生成属性的 setter
。另外,可以在.h
文件中用readonly
修饰在.m
文件类扩展中用readwrite
修饰同一个属性,这样来防止外界篡改该属性。
3. 内存管理特性
5. strong
ARC 属性默认值, 表示属性对所赋的值持有强引用,会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。
6. copy
防止属性被修改。浅拷贝时,属性对所赋的值增加引用计数,深拷贝时,对所赋的值不会增加引用计数,然后再释放旧值即减少旧值的引用计数。一般用来修饰有可变类型子类的对象。如:NSString
,NSArray
,NSDictionary
等。
对于可变对象类型,如NSMutableString
、NSMutableArray
等则不可以使用copy
修饰。因为Foundation
框架提供的这些类都实现了NSCopying
协议,使用copy
方法返回的都是不可变对象,所以使用:strong
。
copy
还被用来修饰block
,在 ARC 默认会用copy
修饰, 一般情况下在block
需要捕获外界数据时该block
就会被分配在堆区,但在MRC环境下由于手动管理引用计数,block
一般被分配在栈区,需要copy
(转移)到堆区来防止野指针错误。
7. weak
表示属性对所赋的值对象持有弱引用,不会增加所赋的新值的引用计数,也不减少旧值的引用计数,但当该值被销毁时,weak
修饰的属性会被自动赋值为nil
,这样就可以避免野指针错误。只能修饰对象。
8. assign
MRC 属性默认值, 对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,常用于基础类型。也可以修饰对象,当对象被销毁时,编译器不会将该属性置为nil
(产生野指针)。
9. unsafe_unretained
对属性只进行简单的赋值操作,不更改所赋的新值的引用计数,也不改变旧值的引用计数,当对象被销毁时,编译器不会将该属性置为nil
(产生野指针)。只能修饰对象。
10. retain
MRC 特性。表示属性对所赋的值会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。
4. 存取方法重命名
11. setter
12. getter
//程序员之间有一个约定, 一般情况下获取 BOOL 类型的属性的值, 我们都会将 getter 方法名称改为: isXXX
@property ( getter = isBlue) BOOL blue;
//存取方法不能以 new 开头,如果你要以 new 开头命名一个属性,会默认生成一个 new 开头的 getter 方法:
//这时候就会报错:Property follows Cocoa naming convention for returning 'owned' objects。
//解决办法,就是用 getter = 重命名 getter 方法
@property (nonatomic, copy, getter = theNewName,setter = XXX) NSString *newName;
5. 为空性
为了更好地和Swift
混编(配合Swift
的optional
类型),在 iOS9
中 Xcode 6.3
,Objective-C
新增了一个语言特性,nullability
。具体就是以下 4 个新特性。
13. nonnull
表示属性不可为空。
在接口中nullable
的是少数,因此为了防止写一大堆nonnull
,Foundation
提供了一对宏,包在宏里面的对象默认加nonnull
修饰符,如果属性可以为空只需将nullable
手动添加。
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
@property (nonatomic, copy) NSString *name1;
@property (nonatomic, assign) NSUInteger age;
@property (nonatomic, copy, nullable) NSString *name2;
//等价
@property (nonatomic, copy) NSString *_Nullable name2;
@end
NS_ASSUME_NONNULL_END
14. nullable
表示属性可以为空。
15. null_resettable
表示属性 setter
参数值 修饰符为 nullable
, getter
返回值 修饰符为 nonable
. 即 , setter
可以为空,getter
不能为空, (比如某些属性提供了默认值,此时使用 null_resettable
修饰属性 )。需重写setter
或getter
其中之一,自己做判断,确保真正返回的值不是nil
。否则报警告:Synthesized setter 'setName:' for null_resettable property 'name' does not handle nil
16. null_unspecified
属性默认值, 表示属性不确定是否为空。