KVC底层原理探究本质-GNUstep

这里是我学习过程中找到的一条探究思路,希望可以开启你的新世纪大门,少走点弯路吧,书籍推荐《Objective-C高级编程 iOS与OS X多线程和内存管理》,这里面有通过GNUStep源码分析了OC的内存管理方面的原理。gnustep-base-1.26.0 源码注释笔记。KVC底层,KVC底层原理,KVC本质,KVC原理,GNUstep

前半部分是结论,后半部分是验证,重点!!!

KVC

KVC全称Key Value Coding(键值编码),是可以通过对象属性名称直接对属性值进行编码(赋值及访问)。而不需要调用明确的存取方法。这样就可以运行时动态访问和修改对象的属性,而不是在编译时确定。

赋值 setValue:forKey:的原理

  1. 按照setKey:、_setKey:的顺序查找方法
  2. 如果找到了方法就传递参数,调用方法
  3. 如果找不到,就去检查是否可以直接访问实例变量(+accessInstanceVariablesDirectly),如果NO就是不可以,那么就调用setValue:forUndefineKey:并抛出异常NSUnknownKeyException
  4. 如果+accessInstanceVariablesDirectly返回YES,就按照_key,_isKey,key,_isKey的顺序查找成员变量
  5. 如果找到了成员变量,就直接赋值。
  6. 如果 _key、_isKey、key、isKey的顺序没有查找到成员变量就调用setValue:forUndefinedKey: 并抛出异常 NSUnknownKeyException

核心:setValue:forKey:->setKey、_setKey->accessInstanceVariablesDirectly->_key、_isKey、key、isKey

取值 valueForKey:的原理

  1. 按照getKey,key,_isKey,_key的顺序查找方法
  2. 如果找到了就传递参数,调用方法
  3. 如果没找到,就检查是否可以直接访问实例变量(+accessInstanceVariablesDirectly),如果不可以,就调用setValue:forUndefineKey:并抛出异常NSUnknownKeyException
  4. 如果+accessInstanceVariablesDirectly返回YES,就按照_key、_isKey、key、isKey的顺序查找成员变量
  5. 如果找到了成员变量,就直接取值
  6. 如果 _key、_isKey、key、isKey的顺序没有查找到成员变量就调用valueForUndefinedKey:并抛出异常NSUnknownKeyException

核心:valueForKey:->getKe、key、_isKey、_key->accessInstanceVariablesDirectly->_key、_isKey、key、isKey

看到这里不要着急,精彩在后面!!!


GNUstep是Cocoa框架的互换框架。也就是说,GNUstep的源代码虽不能说与苹果的Cocoa实现完全相同,但是从使用者的角度来看,两者的行为和实现是一样的,或者说非常相似。理解了GNUstep源代码也就是相当于理解了苹果的Cocoa实现。

static void
SetValueForKey(NSObject *self, id anObject, const char *key, unsigned size)
{
  SEL       sel = 0;
  const char    *type = 0;
  int       off = 0;

  if (size > 0)
    {
      const char    *name;
      char      buf[size + 6];
      char      lo;
      char      hi;
      //C 库函数 char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。
      strncpy(buf, "_set", 4);
      strncpy(&buf[4], key, size);
      lo = buf[4];
      hi = islower(lo) ? toupper(lo) : lo;
      buf[4] = hi;
      buf[size + 4] = ':';
      buf[size + 5] = '\0';

      //到这时buf里面存的字符串是_setKey:,下面遇到的 &buf[1],buf分别代表setKey:,_setKey:
      name = &buf[1];   // setKey:
      type = NULL;
      sel = sel_getUid(name);
      if (sel == 0 || [self respondsToSelector: sel] == NO)
    {
      name = buf;   // _setKey:
      sel = sel_getUid(name);
      if (sel == 0 || [self respondsToSelector: sel] == NO)
        {
          sel = 0;
            //此处判断:是否可以直接访问实例变量,英语重要性
          if ([[self class] accessInstanceVariablesDirectly] == YES)
        {
            //清0 buf,lo = key
          buf[size + 4] = '\0';
          buf[3] = '_';
          buf[4] = lo;
          name = &buf[3];   // _key
          if (GSObjCFindVariable(self, name, &type, &size, &off) == NO)
            {
                //hi = Key
              buf[4] = hi;
              buf[3] = 's';
              buf[2] = 'i';
              buf[1] = '_';
              name = &buf[1];   // _isKey
              if (GSObjCFindVariable(self,
            name, &type, &size, &off) == NO)
            {
              buf[4] = lo;
              name = &buf[4];   // key
              if (GSObjCFindVariable(self,
                name, &type, &size, &off) == NO)
                {
                  buf[4] = hi;
                  buf[3] = 's';
                  buf[2] = 'i';
                  name = &buf[2];   // isKey
                  GSObjCFindVariable(self,
                name, &type, &size, &off);
                }
            }
            }
        }
        //其中任何一次找到key,都会调用GSObjCFindVariable,实现赋值操作。
        }
      else
        {
          GSOnceFLog(@"Key-value access using _setKey: is deprecated:");
        }
    }
    }
  GSObjCSetVal(self, key, anObject, sel, type, size, off);
}
//下面这段代码是KVC取值操作原理
static id ValueForKey(NSObject *self, const char *key, unsigned size)
{
  SEL       sel = 0;
  int       off = 0;
  const char    *type = NULL;

  if (size > 0)
    {
      const char    *name;
      char      buf[size + 5];
      char      lo;
      char      hi;

      strncpy(buf, "_get", 4);
      strncpy(&buf[4], key, size);
      buf[size + 4] = '\0';
      lo = buf[4];
      hi = islower(lo) ? toupper(lo) : lo;
      buf[4] = hi;

      name = &buf[1];   // getKey
      sel = sel_getUid(name);
      if (sel == 0 || [self respondsToSelector: sel] == NO)
    {
      buf[4] = lo;
      name = &buf[4];   // key
      sel = sel_getUid(name);
      if (sel == 0 || [self respondsToSelector: sel] == NO)
        {
              buf[4] = hi;
              buf[3] = 's';
              buf[2] = 'i';
              name = &buf[2];   // isKey
              sel = sel_getUid(name);
              if (sel == 0 || [self respondsToSelector: sel] == NO)
                {
                  sel = 0;
                }
        }
    }

      if (sel == 0 && [[self class] accessInstanceVariablesDirectly] == YES)
    {
      buf[4] = hi;
      name = buf;   // _getKey
      sel = sel_getUid(name);
      if (sel == 0 || [self respondsToSelector: sel] == NO)
        {
          buf[4] = lo;
          buf[3] = '_';
          name = &buf[3];   // _key
          sel = sel_getUid(name);
          if (sel == 0 || [self respondsToSelector: sel] == NO)
        {
          sel = 0;
        }
        }
      if (sel == 0)
        {
          if (GSObjCFindVariable(self, name, &type, &size, &off) == NO)
        {
                  buf[4] = hi;
                  buf[3] = 's';
                  buf[2] = 'i';
                  buf[1] = '_';
                  name = &buf[1];   // _isKey
          if (!GSObjCFindVariable(self, name, &type, &size, &off))
                    {
                       buf[4] = lo;
                       name = &buf[4];      // key
               if (!GSObjCFindVariable(self, name, &type, &size, &off))
                         {
                            buf[4] = hi;
                            buf[3] = 's';
                            buf[2] = 'i';
                            name = &buf[2]; // isKey
                            GSObjCFindVariable(self, name, &type, &size, &off);
                         }
                    }
        }
        }
    }
    }
  return GSObjCGetVal(self, key, sel, type, size, off);
}

赋值setValue:forKeyPath原理

- (void) setValue: (id)anObject forKeyPath: (NSString*)aKey
{
    //以.切割keyPath,递归调用setValue:forKeyPath:方法逐步递归取值,知道最后不能再递归就赋值
  NSRange       r = [aKey rangeOfString: @"." options: NSLiteralSearch];
#ifdef WANT_DEPRECATED_KVC_COMPAT
  IMP           o = [self methodForSelector: @selector(takeValue:forKeyPath:)];

  setupCompat();
  if (o != takePath && o != takePathKVO)
    {
      (*o)(self, @selector(takeValue:forKeyPath:), anObject, aKey);
      return;
    }
#endif

  if (r.length == 0)
    {
      [self setValue: anObject forKey: aKey];
    }
  else
    {
      NSString  *key = [aKey substringToIndex: r.location];
      NSString  *path = [aKey substringFromIndex: NSMaxRange(r)];
      
      [[self valueForKey: key] setValue: anObject forKeyPath: path];
    }
}

上面截取的代码是KVC赋值与取值的操作,主要解读了下赋值的操作。有人会觉得毕竟两个框架不一样,需要验证下:这里我只描述下如何验证原理,网上很多博客KVC底层原理的分析,都只是验证,我这里是配合类源码分析了下。
场景:对一个Person类中的name属性进行KVC赋值与取值。赋值,我们在这个Person类中,使用赋值操作原理中提到的方法和成员变量覆盖掉Person类隐藏的,然后逐个验证。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335