KVC底層原理探究本質(zhì)-GNUstep

這里是我學(xué)習(xí)過程中找到的一條探究思路公你,希望可以開啟你的新世紀(jì)大門,少走點(diǎn)彎路吧假瞬,書籍推薦《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》陕靠,這里面有通過GNUStep源碼分析了OC的內(nèi)存管理方面的原理。gnustep-base-1.26.0 源碼注釋筆記脱茉。KVC底層剪芥,KVC底層原理,KVC本質(zhì)芦劣,KVC原理,GNUstep

前半部分是結(jié)論说榆,后半部分是驗(yàn)證虚吟,重點(diǎn)!G┎啤串慰!

KVC

KVC全稱Key Value Coding(鍵值編碼),是可以通過對(duì)象屬性名稱直接對(duì)屬性值進(jìn)行編碼(賦值及訪問)唱蒸。而不需要調(diào)用明確的存取方法邦鲫。這樣就可以運(yùn)行時(shí)動(dòng)態(tài)訪問和修改對(duì)象的屬性,而不是在編譯時(shí)確定。

賦值 setValue:forKey:的原理

  1. 按照setKey:庆捺、_setKey:的順序查找方法
  2. 如果找到了方法就傳遞參數(shù)古今,調(diào)用方法
  3. 如果找不到,就去檢查是否可以直接訪問實(shí)例變量(+accessInstanceVariablesDirectly)滔以,如果NO就是不可以捉腥,那么就調(diào)用setValue:forUndefineKey:并拋出異常NSUnknownKeyException
  4. 如果+accessInstanceVariablesDirectly返回YES,就按照_key,_isKey,key,_isKey的順序查找成員變量
  5. 如果找到了成員變量你画,就直接賦值抵碟。
  6. 如果 _key、_isKey坏匪、key拟逮、isKey的順序沒有查找到成員變量就調(diào)用setValue:forUndefinedKey: 并拋出異常 NSUnknownKeyException

核心:setValue:forKey:->setKey、_setKey->accessInstanceVariablesDirectly->_key适滓、_isKey敦迄、key、isKey

取值 valueForKey:的原理

  1. 按照getKey,key,_isKey,_key的順序查找方法
  2. 如果找到了就傳遞參數(shù)粒竖,調(diào)用方法
  3. 如果沒找到颅崩,就檢查是否可以直接訪問實(shí)例變量(+accessInstanceVariablesDirectly),如果不可以蕊苗,就調(diào)用setValue:forUndefineKey:并拋出異常NSUnknownKeyException
  4. 如果+accessInstanceVariablesDirectly返回YES沿后,就按照_key、_isKey朽砰、key尖滚、isKey的順序查找成員變量
  5. 如果找到了成員變量,就直接取值
  6. 如果 _key瞧柔、_isKey漆弄、key、isKey的順序沒有查找到成員變量就調(diào)用valueForUndefinedKey:并拋出異常NSUnknownKeyException

核心:valueForKey:->getKe造锅、key撼唾、_isKey、_key->accessInstanceVariablesDirectly->_key哥蔚、_isKey倒谷、key、isKey

看到這里不要著急糙箍,精彩在后面2吵睢!深夯!


GNUstep是Cocoa框架的互換框架抖格。也就是說,GNUstep的源代碼雖不能說與蘋果的Cocoa實(shí)現(xiàn)完全相同,但是從使用者的角度來看雹拄,兩者的行為和實(shí)現(xiàn)是一樣的收奔,或者說非常相似。理解了GNUstep源代碼也就是相當(dāng)于理解了蘋果的Cocoa實(shí)現(xiàn)办桨。

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 庫(kù)函數(shù) char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串復(fù)制到 dest筹淫,最多復(fù)制 n 個(gè)字符。當(dāng) src 的長(zhǎng)度小于 n 時(shí)呢撞,dest 的剩余部分將用空字節(jié)填充损姜。
      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';

      //到這時(shí)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;
            //此處判斷:是否可以直接訪問實(shí)例變量,英語(yǔ)重要性
          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棒卷,都會(huì)調(diào)用GSObjCFindVariable,實(shí)現(xiàn)賦值操作祝钢。
        }
      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比规,遞歸調(diào)用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賦值與取值的操作拦英,主要解讀了下賦值的操作蜒什。有人會(huì)覺得畢竟兩個(gè)框架不一樣,需要驗(yàn)證下:這里我只描述下如何驗(yàn)證原理疤估,網(wǎng)上很多博客KVC底層原理的分析灾常,都只是驗(yàn)證,我這里是配合類源碼分析了下铃拇。
場(chǎng)景:對(duì)一個(gè)Person類中的name屬性進(jìn)行KVC賦值與取值钞瀑。賦值,我們?cè)谶@個(gè)Person類中慷荔,使用賦值操作原理中提到的方法和成員變量覆蓋掉Person類隱藏的雕什,然后逐個(gè)驗(yàn)證。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末显晶,一起剝皮案震驚了整個(gè)濱河市贷岸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吧碾,老刑警劉巖凰盔,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件墓卦,死亡現(xiàn)場(chǎng)離奇詭異倦春,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門睁本,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尿庐,“玉大人,你說我怎么就攤上這事呢堰〕” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵枉疼,是天一觀的道長(zhǎng)皮假。 經(jīng)常有香客問我,道長(zhǎng)骂维,這世上最難降的妖魔是什么惹资? 我笑而不...
    開封第一講書人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮航闺,結(jié)果婚禮上褪测,老公的妹妹穿的比我還像新娘。我一直安慰自己潦刃,他們只是感情好侮措,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著乖杠,像睡著了一般分扎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滑黔,一...
    開封第一講書人閱讀 51,775評(píng)論 1 307
  • 那天笆包,我揣著相機(jī)與錄音,去河邊找鬼略荡。 笑死庵佣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汛兜。 我是一名探鬼主播巴粪,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼粥谬!你這毒婦竟也來了肛根?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤漏策,失蹤者是張志新(化名)和其女友劉穎派哲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掺喻,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡芭届,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年储矩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片褂乍。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡持隧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逃片,到底是詐尸還是另有隱情屡拨,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布褥实,位于F島的核電站呀狼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏损离。R本人自食惡果不足惜赠潦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一岸售、第九天 我趴在偏房一處隱蔽的房頂上張望锁荔。 院中可真熱鬧,春花似錦别厘、人聲如沸怎棱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拳恋。三九已至凡资,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間谬运,已是汗流浹背隙赁。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梆暖,地道東北人伞访。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像轰驳,于是被迫代替她去往敵國(guó)和親厚掷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356