阅读 569

KVC 深入学习和探究

直入主题,开头先介绍下本篇学习和探究方向,首先搞清楚成员变量、实例变量、属性的定义,以便KVC赋值取值时能够轻松区分;其次深入探究KVC取值原理、赋值原理;再次通过对YYmodel源码的分析,深入理解KVC;最后再探究一下Category的实现原理及其使用时的注意点。

一、成员变量,实例变量,属性的区别

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController
//{}内的全部为成员变量,实例变量是一种特殊的成员变量,是由OC中的类声明的成员变量
{
    UIButton *button;
    int count;
    id data;
}
//GCC --> LLVM 属性
//1.默认的setter + getter
//2.自动创建一个带下划线的实例变量匹配属性
@property (nonatomic, strong) UIButton *myButton;
@end

@implementation ViewController
@synthesize  myButton = _myButton; //指定与属性对应的实例变量
编译器期间,让编译器自动生成getter/setter方法。
当有自定义的存或取方法时,自定义会屏蔽自动生成该方法

@dynamic  myButton;
告诉编译器,不自动生成属性对应的实例变量和访问方法(getter/setter),避免编译期间产生警告;
然后由自己实现存取方法或存取方法在运行时动态创建绑定

@end
复制代码

在{ }中声明的变量都是成员变量;按照上面的例子:button count data都是成员变量;

实例变量本质上就是成员变量,只是实例是针对类而言,实例是指类的声明;

按照上面的例子:button是实例变量 data也是实例变量,因为id是OC特有的类,本质上来说id等同于(void *);

实例变量的英文翻译为 Instance Variable (object-specific storage)

实例的英文翻译为 Instance (manifestation of a class)说的是"类的表现",说明实例变量应该是由类定义的变量!

除去基本数据类型int float ....等,其他类型的变量都叫做实例变量;

  1. 成员变量 = 实例变量 + 基本数据类型变量,成员变量是定义在{}号中的变量,如果变量的数据类型是一个类则称这个变量为实例变量;
  2. 成员变量用于内部,无需与外界接触的变量,因为成员变量不会生成set、get方法,所以外界无法与成员变量接触;
  3. 由于实例变量是成员变量的一种特殊情况,所以实例变量也是类内部使用的,无需与外部接触的变量,这个也就是所谓的类私有变量。
  4. 根据成员变量的私有性,为了方便访问,所以就有了属性变量;属性变量是用于与其他对象交互的变量;

属性变量的好处就是允许让其他对象访问到该变量,因为属性创建过程中自动产生了set方法和get方法;当然,你可以设置只读或者可写等,设置方法也可自定义。所以,属性变量是用于与其他对象交互的变量。

二、Key-Value Coding 键值编码机制

键值编码是一种机制,通过NSKeyValueCoding非正式协议,对象采用这种机制提供对其属性的间接访问。当对象符合键值编码时,它的属性可以使用字符串参数通过简明、统一的消息接口进行寻址。这种间接访问机制补充了实例变量及其关联的访问器方法提供的直接访问。 Key-Value Coding Programming Guide 官方文档

  1. 赋值过程--- setValue:forKey:的默认实现及验证
    KVC赋值原理图
    • 1.按照顺序查找第一个名为set<Key>:_set<Key>:的方法。如果找到, 传入value值并调用。
      • 1.1 同时写了set<Key>:``_set<Key>:两个方法,优先调用set<Key>:方法;
      • 1.2 注释掉set<Key>:方法后,就调用了_set<Key>:方法,证实了KVC的前期赋值情况!
    • 2.如果找不到简单访问器,并且类方法accessInstanceVariablesDirectly返回YES, 则按以下顺序查找实例变量:_<key>_is<Key><key>is<Key> 。如果找到, 则直接使用value值设置变量并完成。
      • 2.1如果两个方法都没有实现,此时KVC会看accessInstanceVariablesDirectly方法,返回Yes代表可以直接访问成员变量,反之不能访问成员变量!如果返回为Yes,会按照_key、_isKey、key、isKey成员属性进行赋值,当四个成员变量同时出现,首先给_key赋值;
      • 2.2 将_key成员变量注释掉后,就优先给_isKey赋值,优先级仅次于_key;
      • 2.3 将_isKey注释掉之后,发现给key赋值;
      • 2.4 将key成员变量注释掉之后,最后给isAge赋值,符合了上述setValue:forkey的访问成员变量的优先级 _key > _isKey > key > isKey的顺序。
    • 3.如果找不到以上方法或实例变量,则调用setValue:forUndefinedKey:。默认情况下,这会引发异常,但NSObject的子类可以通过重载并根据特定key做一些特殊处理。
  2. 取值过程--- valueForKey:的默认实现及验证
    KVC取值原理图
    • 1.按照顺序搜索名称为get<Key><key>is<Key>_<key>的第一个访问器方法。如果找到,调用它并继续到步骤3。否则请继续执行下一步。
      • ViewController 中的代码如下:
      @implementation ViewController
      - (void)viewDidLoad {
          [super viewDidLoad];
          _p = [[YNPerson alloc]init];
          NSLog(@"%@",[_p valueForKey:@"name"]);
      }
      @end
      复制代码
      • 1.1 当有四个方法时,会优先调用get<Key>方法;
      • 1.2 将get<Key>方法注释掉后,调用了<key>方法,验证了get<Key>><key>
      • 1.3 将get<Key><key>方法注释掉后,调用了is<Key>方法,验证了get<Key>> <key> > is<Key>;
      • 1.4 将get<Key><key>方法以及is<Key>注释掉后,调用了_<key>方法,验证了get<Key> > <key> > is<Key> > _<key>;
    • 2.如果找不到简单访问器方法,并且消息接收者的类方法accessInstanceVariablesDirectly返回YES,则系统按以下顺序搜索名为:_<key>_is<Key><key>is<Key>的实例变量。如果找到,则直接获取实例变量的值,然后继续执行步骤3。否则, 继续跳转到步骤4。
      • YNPerson.h中添加四个成员变量:
      @interface YNPerson : NSObject
      {
          @public 
          NSString *_name;
          NSString *_isName;
          NSString *name;
          NSString *isName;
      }
      @end
      复制代码
      • ViewController 中的调用代码如下:
      @implementation ViewController
      - (void)viewDidLoad {
          [super viewDidLoad];
          _p = [[YNPerson alloc]init];
          _p->_name   = @"_name";
          _p->_isName = @"_isName";
          _p->name    = @"name";
          _p->isName  = @"isName";
          NSLog(@"%@",[_p valueForKey:@"name"]);
      }
      @end
      复制代码
      • 2.1 四个成员变量都存在,优先取_<key>成员变量的值;
      • 2.2 没有_<key>成员变量时(注释掉成员变量声明和赋值),取_is<Key>的值;
      • 2.3 没有_<key>_is<Key>成员变量时(注释掉成员变量声明和赋值),取<key>的值;
      • 2.4 没有_<key>_is<Key><key>成员变量时(注释掉成员变量声明和赋值),取is<Key>的值;
    • 3.如果获取到的属性值是对象指针,即获取的是对象,则直接将对象返回。如果获取到的属性值是NSNumber支持的数据类型,则将其存储在NSNumber实例并返回。如果获取到的属性值不是 NSNumber 支持的类型, 则转换为NSValue对象, 然后返回。
    • 4.如果上述所有方法都没有执行,则调用valueForUndefinedKey:。默认情况下,这会引发异常,但NSObject的子类可以通过重载并根据特定key做一些特殊处理。

三、YYModel 原理分析

关于此部分内容,请移步至YYModel 原理分析,欢迎查阅、指正。

四、Category的实现原理及其使用

关于此部分内容,请移步至Category的实现原理及其使用,欢迎查阅、指正。

由于本人水平有限,文中如有不足之处,望大神指出。
如果你看完后觉得对你有所帮助,勿忘点赞+关注
附本文的Demo,赠人玫瑰,手有余香。