属性
属性的定义
Objective-C 2.0定义的语法, 为实例变量提供了setter getter方法的默认实现
能在一定程度上简化程序代码
声明属性关键字@property
如果实例变量为_xxx 声明为属性时把下划线去掉写成xxx
声明成属性 代表系统默认帮助我们实现了setter和getter方法的声明
声明中:
@property NSString *name;
(此时称name为属性而不是实例变量) NSString * 是set方法的参数类型和get方法的返回值类型
相当于我们写的
{
NSString *_name;
}
- (void)setName:(NSString *)name;
- (NSString *)name;
属性的属性
Objective-C提供属性的⺫的是为了简化程序员编码
为属性提供了⼀些关键字⽤以控制setter、getter的实现细节
这些关键字我们称为属性的属性(attribute)
⼀共3⼤类attribute。
1. 第⼀类读写性控制(readonly、readwrite、setter、getter)
readonly, 只读
告诉编译器, 只声明getter⽅法 (⽆setter⽅法)。
例如:@property(readonly)NSString *name;
等价于
- (NSString *)name;
利用readonly属性的声明的属性, 不能够修改该实例变量的值, 只能够在初始化的时候进行赋值readwrite, 又能读又能写 (如果不声明属性的属性 默认是readwrite属性)
告诉编译器, 既声明setter⼜声明getter。
例如:@property(readwrite)NSString *name;
等价于- (NSString *)name; - (void)setName:(NSString *)name;
setter, 设定指定的set方法方法名 不写本属性, 默认生成的方法名是setXxx
用法:@property (setter = aa:)
指定生成的setter方法方法名是 aa:
由于需要带参数标识的:
写起来比较麻烦, 通常选择修改getter方法
所以本属性常用性不高getter, 设定指定的get方法方法名 不写本属性, 默认生成的方法名是xx
用法: @property (getter = bb)
指定生成的getter方法方法名是bb
getter属性较为常用
比如
@property (getter = isOpening) BOOL opening;
因为若把实例变量的属性名写成 BOOL isOpening;
它的setter方法方法名为 setIsOpening. 不太符合语境
2. 第⼆类: 原⼦性控制(nonatomic、atomic)
atomic
setter、getter⽅法在多线程访问下是绝对安全的,即 setter、getter内部做了多线程访问处理。
原⼦性控制的默认设置是 atomic
在多个线程同时访问这个实例变量的时候, 用atomic可以防止线程之间的互相扰乱nonatomic
setter、getter⽅法内部不会做多线程访问处理,仅仅是 普通的setter、getter⽅法
程序开发过程中,setter、getter处处都在⽤
如果使⽤atomic,需要不断的对setter、getter加锁解锁以保证线程访问安全,会很占⽤系统资源,降低系统性能。
通常设置为nonatomic,某些属性需要线程安全的时候,才定义为atomic.
例如:
@property (readwrite,nonatomic)NSString *name;
等价于
- (NSString *)name;
- (void)setName:(NSString *)name;
3. 第三类:语义设置 (assign、retain、copy)
assign
setter、getter内部实现是直接赋值。
例如:@property(nonatomic,assign)NSString *name; - (void)setName:(NSString *)name{ _name = name; } - (NSString *)name{ return _name; }
assign 通常是修饰基本数据类型的变量
retain
setter、getter的内部实现会做内存优化。
例如:
@property(nonatomic,retain)NSString *name;
相当于:- (void)setName:(NSString *)name{ if(_name != name) { [_name release]; // release释放, 与C语言中的标记删除free不同 _name = [name retain]; } } - (NSString *)name{ return [[_name retain] autorelease]; }
retain 通常是修饰对象类型的变量 不能够修饰基本数据类型
copy
setter、getter的内部实现也会做内存优化。
例如:@property(nonatomic,copy)NSString *name; - (void)setName:(NSString *)name{ if(_name != name){ [_name release]; _name = [name copy]; // 和retain的实现不同的地方. } } - (NSString *)name{ return [[_name retain]autorelease]; }
使用copy修饰的对象, 必须得是遵守了NSCopying协议的类的对象
4. 总结
语义设置主要是控制setter getter方法的实现部分.
assign一般用于声明基本数据类型的使用
retain 内部实现对内存管理的优化, 一般用于声明对象类型的使用
copy 和retain相似, 但是声明成copy的对象类型必须是遵守了NSCopying协议的类对象
assign, retain, copy 一个属性的声明时, 三者只能使用其中的一个属性, 不能同时使用多个
注意, copy和retain的setter getter的具体实现只能写在MRC工程里, ARC工程里是无法写具体实现的.创建工程 默认是ARC状态(自动引用技术) 需要手动关闭ARC才可以切换至MRC
5. 例题
使用属性的属性声明实例变量
// .h
// 姓名
@property(nonatomic, retain) NSString *name; // 读写属性省略默认是readwrite, 声明原子性为非线程保护
// 年龄
@property(nonatomic, assign) NSInteger age; // 实例变量是基本数据类型, 根据语义设置成assign
// 性别
@property(readonly, nonatomic, retain) NSString *sex; // 读写属性设置成只读
- (instancetype)initWithName:(NSString *)name
age:(NSInteger)age
sex:(NSString *)sex;
+ (instancetype)studentWithName:(NSString *)name
age:(NSInteger)age
sex:(NSString *)sex;
- (instancetype)initWithName:(NSString *)name
age:(NSInteger)age
sex:(NSString *)sex {
self = [super init];
if (self) {
_name = name;
_age = age;
_sex = sex;
}
return self;
}
+ (instancetype)studentWithName:(NSString *)name
age:(NSInteger)age
sex:(NSString *)sex {
Student *stu = [[Student alloc] initWithName:name age:age sex:sex];
return stu;
}
6. 点语法
点语法 提供了一个快捷的访问属性的一种方法
和java的点语法不同, java是类对象调用本类方法的时候用’.’
而OC中是用’.’通过setter和getter访问属性, 并且只能调用属性声明的实例变量. 其他方法不能够通过点来调用.
NSLog(@"%@", stu.name); // 相当于调用了实例变量_name的getter方法
stu.name = @"hahah"; // 相当于调用了实例变量_name的setter方法
点语法出现在等号左边调用的是setter方法用于赋值, 等号右边调用的是getter方法用于取值.
tips:
若是要想重写属性的setter方法. 有两种方法可选:
一个在.h文件中把需要重写的属性的实例变量声明出来
然后在.m文件重写setter方法
这时候setter 和getter方法的操作对像都是实例变量_xxx
.另一个方法是在.m文件中用关键字@synthesize用
@synthesize name = _name;
的形式, 把属性和实例变量名关联起来. 声明对属性name的操作都是在对实例变量_name
进行的操作. 如果声明中没有实例变量_name
, 本句语句会自动添加一个实例变量_name
.
原因是, 在取值的时候, 系统会识别实例变量名_xxx, 自动取出属性名xxx保存的值返回.
但是在赋值的时候, 系统无法找到_xxx的声明空间, 如果以上两种方法都没有选择的话, 会导致setter方法中的赋值语句找不到被赋值变量(报红), 最后编译失败.
KVC
KVC(Key-Value-Coding),键值编码,是⼀种间接访问实例变量的⽅法。
key:键,⽤于标识实例变量
vlaue:实例变量对应的值
主要的赋值方法有:
// 赋值方法1
- (void)setValue:(id)value forKey:(NSString *)key;
// 赋值方法2
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
// 赋值方法3
- (void)setValuesForKeysWithDictionary:(NSDictionary *)keyedValues;
取值方法有:
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (id)valueForUndefinedKey:(NSString *)key;
赋值方法1的使用:
value必须是一个对象, key是类中的属性名
若被赋值的属性是一个NSInteger类型的属性, 也需要将值转化成NSNumber类放入value的位置. 系统会自动将NSNumber转化成NSInteger存入属性中.
若是属性名key 因为写错或者未在类中写出的原因, 导致本方法无法在类中找到对应的属性名, 系统会抛出一个异常, 然后调用- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
方法对这个异常进行处理.
如果此类或者其父类中未实现本方法. 且输入的key未被在类中找到, 程序就会因为找不到这个异常处理的方法崩溃.
若是value传入了无值(nil). 系统也会因为找到了key但是没有对应的数值传入抛出一个无值异常. 系统会调用- (void)setNilValueForKey:(NSString *)key;
对这个异常进行处理. 和属性名未找到异常一样, 若是没有在本类或者父类的方法, 系统会因为找不到执行方法的实现崩溃.
赋值方法2的使用:
value和方法1一样, 必须是一个对象. keyPath表示可以传入一个路径
比如说类A中存在复合, 有一个属性为类B的对象b, 而类B中也有一个属性name
此时可以通过类A的对象直接调用方法2 传入一个路径 b.name
, 直接对类A中的类B对象的属性name进行赋值.
值得注意的, 在进行此操作的时候, 对象b必须是已初始化过的. 否则无法访问b的属性name.
赋值方法3的使用:
KVC本质上也是通过属性名和值一一对应的关系来进行取值赋值. 而字典也有同样的特性.
字典的key和KVC的key对应, 字典的value和KVC的value对应. 即可将实现将一组保存在字典的数据 通过KVC写入对象中.
本方法在从后台获得值, 写入model层数据的时候十分常用.
比如:
// Model.h中的属性声明
@interface Model : NSObject
@property (nonatomic, retain)NSString *data1;
@property (nonatomic, retain)NSString *data2;
@end
// main.m文件中的实现
NSDictionary *dic = @{@"key1":@"value1", @"key2":@"value2"};
Model *model = [[Model alloc] init];
[model setValuesForKeysWithDictionary:dic];
通过赋值方法3, 就可以将一组保存在字典里的数据写入类的对象中. 注意此时若是找不到key值 或者传入nil值依然会调用
- (void)setValue:(id)value forUndefinedKey:(NSString *)key;
和
- (void)setNilValueForKey:(NSString *)key;
方法进行异常处理.
注: 在类中查找key的顺序是: _key _isKey key isKey.