爬爬爬之路:OC语言(八) 属性 KVC简单介绍

来源:互联网 发布:湖州淘宝美工培训 编辑:程序博客网 时间:2024/05/03 09:10

属性

属性的定义

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)

  1. readonly, 只读
    告诉编译器, 只声明getter⽅法 (⽆setter⽅法)。
    例如:@property(readonly)NSString *name;
    等价于
    - (NSString *)name;
    利用readonly属性的声明的属性, 不能够修改该实例变量的值, 只能够在初始化的时候进行赋值

  2. readwrite, 又能读又能写 (如果不声明属性的属性 默认是readwrite属性)
    告诉编译器, 既声明setter⼜声明getter。
    例如: @property(readwrite)NSString *name;
    等价于

    - (NSString *)name;- (void)setName:(NSString *)name;
  3. setter, 设定指定的set方法方法名 不写本属性, 默认生成的方法名是setXxx
    用法: @property (setter = aa:)
    指定生成的setter方法方法名是 aa:
    由于需要带参数标识的:
    写起来比较麻烦, 通常选择修改getter方法
    所以本属性常用性不高

  4. getter, 设定指定的get方法方法名 不写本属性, 默认生成的方法名是xx
    用法: @property (getter = bb)
    指定生成的getter方法方法名是bb
    getter属性较为常用
    比如
    @property (getter = isOpening) BOOL opening;
    因为若把实例变量的属性名写成 BOOL isOpening;
    它的setter方法方法名为 setIsOpening. 不太符合语境

2. 第⼆类: 原⼦性控制(nonatomic、atomic)

  1. atomic
    setter、getter⽅法在多线程访问下是绝对安全的,即 setter、getter内部做了多线程访问处理。
    原⼦性控制的默认设置是 atomic
    在多个线程同时访问这个实例变量的时候, 用atomic可以防止线程之间的互相扰乱

  2. 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)

  1. assign
    setter、getter内部实现是直接赋值。
    例如:

    @property(nonatomic,assign)NSString *name;- (void)setName:(NSString *)name{     _name = name;}- (NSString *)name{     return _name;}

    assign 通常是修饰基本数据类型的变量

  2. 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 通常是修饰对象类型的变量 不能够修饰基本数据类型

  3. 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方法. 有两种方法可选:

  1. 一个在.h文件中把需要重写的属性的实例变量声明出来
    然后在.m文件重写setter方法
    这时候setter 和getter方法的操作对像都是实例变量_xxx.

  2. 另一个方法是在.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.

1 0
原创粉丝点击