GNUstep KVC/KVO探索(一):KVC的內(nèi)部實(shí)現(xiàn)

GNUstep KVC/KVO探索(一):KVC的內(nèi)部實(shí)現(xiàn)
GNUstep KVC/KVO探索(二):KVO的內(nèi)部實(shí)現(xiàn)

KVC全稱是Key Value Coding,定義在NSKeyValueCoding.h文件中镇草,是一個非正式協(xié)議。KVC提供了一種間接訪問其屬性方法或成員變量的機(jī)制屁商,可以通過字符串來訪問對應(yīng)的屬性方法或成員變量。

在NSKeyValueCoding中提供了KVC通用的訪問方法颈墅,分別是getter方法valueForKey:和setter方法setValue:forKey:蜡镶,以及其衍生的keyPath方法雾袱,這兩個方法各個類通用的。并且由KVC提供默認(rèn)的實(shí)現(xiàn)官还。

GNUstep和Cocoa都是基于OpenStep演變而來芹橡,所以底層實(shí)現(xiàn)有很多相似之處,可以通過GNUstep源碼來分析KVC的底層實(shí)現(xiàn)望伦。

一林说、Set方法

- (void) setValue: (id)anObject forKey: (NSString*)aKey
{
  unsigned  size = [aKey length] * 8;
  char      key[size + 1];

  ....

  [aKey getCString: key
     maxLength: size + 1
      encoding: NSUTF8StringEncoding];
  size = strlen(key);
  SetValueForKey(self, anObject, key, size);
}
  1. 將傳入的key轉(zhuǎn)成字符數(shù)組,然后調(diào)用SetValueForKey()屯伞,主要實(shí)現(xiàn)內(nèi)容在SetValueForKey()
  2. 中間省略的部分為老版本的兼容設(shè)置
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;

      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';

      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)
        {
          buf[size + 4] = '\0';
          buf[3] = '_';
          buf[4] = lo;
          name = &buf[3];   // _key
          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) == 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);
                }
            }
            }
        }
        }
      else
        {
          GSOnceFLog        (@"Key-value access using _setKey: is deprecated:");
        }
    }
    }
  GSObjCSetVal(self, key, anObject, sel, type, size, off);
}

此方法的實(shí)現(xiàn)很簡單腿箩,主要是獲取key相關(guān)的方法或者變量,然后調(diào)用GSObjCSetVal()進(jìn)行實(shí)際賦值操作

  1. 方法中利用傳進(jìn)來的字符數(shù)組劣摇,進(jìn)行拼接珠移,依次查詢setKey--->_setKey--->_key--->_isKey--->key--->isKey
  2. accessInstanceVariablesDirectly通過此參數(shù)判斷當(dāng)關(guān)聯(lián)方法找不到時,是否可以直接操作實(shí)例變量末融,默認(rèn)實(shí)現(xiàn)為YES
  3. 最后GSObjCSetVal()傳入查詢到的方法或者變量剑梳,進(jìn)行實(shí)際賦值操作
void
GSObjCSetVal(NSObject *self, const char *key, id val, SEL sel,
  const char *type, unsigned size, int offset)
{
  static NSNull     *null = nil;
  NSMethodSignature *sig = nil;

  if (null == nil)
    {
      null = [NSNull new];
    }
  if (sel != 0)
    {
      sig = [self methodSignatureForSelector: sel];
      if ([sig numberOfArguments] != 3)
    {
      [NSException raise: NSInvalidArgumentException
              format: @"key-value set method has wrong number of args"];
    }
      type = [sig getArgumentTypeAtIndex: 2];
    }
  if (type == NULL)
    {
      [self setValue: val forUndefinedKey:
    [NSString stringWithUTF8String: key]];
    }
  else if ((val == nil || val == null) && *type != _C_ID && *type != _C_CLASS)
    {
      [self setNilValueForKey: [NSString stringWithUTF8String: key]];
    }
  else
    {
      switch (*type)
    {
      case _C_ID:
      case _C_CLASS:
        {
          id    v = val;

          if (sel == 0)
        {
          id *ptr = (id *)((char *)self + offset);

          ASSIGN(*ptr, v);
        }
          else
        {
          void  (*imp)(id, SEL, id) =
            (void (*)(id, SEL, id))[self methodForSelector: sel];

          (*imp)(self, sel, val);
        }
        }
        break;

      case _C_CHR:
        {
          char  v = [val charValue];

          if (sel == 0)
        {
          char *ptr = (char *)((char *)self + offset);

          *ptr = v;
        }
          else
        {
          void  (*imp)(id, SEL, char) =
            (void (*)(id, SEL, char))[self methodForSelector: sel];

          (*imp)(self, sel, v);
        }
        }
        break;

      case _C_UCHR:
      case _C_UCHR:
      case _C_SHT:
      case _C_USHT:
      case _C_INT:

...

      default:
            [self setValue: val forUndefinedKey:
          [NSString stringWithUTF8String: key]];
    }
    }
}

此方法為KVC中實(shí)際賦值方法,主要做了一下幾件事:

  1. 判斷方法是否存在滑潘,如果存在,判斷方法類型是否正確锨咙,set方法的參數(shù)應(yīng)該有三個self _cmd value, 不正確就拋出異常语卤, 如果方法正確,則通過getArgumentTypeAtIndex獲取到方法中value參數(shù)的類型
  2. 此時如果方法存在酪刀,則獲取到方法中參數(shù)的類型粹舵,如果方法不存在,則會使用實(shí)參傳遞進(jìn)來的實(shí)例變量的類型
  3. 判斷類型是否為空骂倘,如果為空眼滤,則代表沒有key相關(guān)的信息,調(diào)用setValue:forUndefinedKey:方法历涝,方法默認(rèn)實(shí)現(xiàn)為拋出異常诅需,子類實(shí)現(xiàn)此方法可避免崩潰
  4. 然后判斷要賦值的value如果為nil/null,并且參數(shù)類型不是指針類型和類,則調(diào)用setNilValueForKey:,默認(rèn)實(shí)現(xiàn)為拋出異常
  5. 此時已經(jīng)獲取了所有信息荧库,判斷參數(shù)類型堰塌,在根據(jù)獲取的相關(guān)聯(lián)信息,是方法還是實(shí)例變量進(jìn)行不同操作分衫,
    如果是實(shí)例變量场刑,則將參數(shù)轉(zhuǎn)成相匹配類型,然后利用指針賦值
    如果是方法蚪战,則根據(jù)methodForSelector:獲取相應(yīng)類型的IMP指針進(jìn)行調(diào)用牵现。
  6. 最后如果類型匹配不到铐懊,則調(diào)用setValue:forUndefinedKey:方法拋出異常

二、get方法

- (id) valueForKey: (NSString*)aKey
{
  unsigned  size = [aKey length] * 8;
  char      key[size + 1];

  [aKey getCString: key
     maxLength: size + 1
      encoding: NSUTF8StringEncoding];
  size = strlen(key);
  return ValueForKey(self, key, size);
}

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);
}

id
GSObjCGetVal(NSObject *self, const char *key, SEL sel,
           const char *type, unsigned size, int offset)
{
  NSMethodSignature *sig = nil;

  if (sel != 0)
    {
      sig = [self methodSignatureForSelector: sel];
      if ([sig numberOfArguments] != 2)
    {
      [NSException raise: NSInvalidArgumentException
              format: @"key-value get method has wrong number of args"];
    }
      type = [sig methodReturnType];
    }
  if (type == NULL)
    {
      return [self valueForUndefinedKey: [NSString stringWithUTF8String: key]];
    }
  else
    {
      id    val = nil;

      switch (*type)
    {
      case _C_ID:
      case _C_CLASS:
        {
          id    v;

          if (sel == 0)
        {
          v = *(id *)((char *)self + offset);
        }
          else
        {
          id    (*imp)(id, SEL) =
            (id (*)(id, SEL))[self methodForSelector: sel];

          v = (*imp)(self, sel);
        }
          val = v;
        }
        break;
      case _C_UCHR:
      case _C_UCHR:
      case _C_SHT:
      case _C_USHT:
      case _C_INT:
....

      default:
#ifdef __GNUSTEP_RUNTIME__
        {
          Class     cls;
          struct objc_slot  *type_slot;
          SEL       typed;
          struct objc_slot  *slot;

          cls = [self class];
          type_slot = objc_get_slot(cls, @selector(retain));
          typed = GSSelectorFromNameAndTypes(sel_getName(sel), NULL);
          slot = objc_get_slot(cls, typed);
          if (strcmp(slot->types, type_slot->types) == 0)
        {
          return slot->method(self, typed);
        }
        }
#endif
        val = [self valueForUndefinedKey:
          [NSString stringWithUTF8String: key]];
    }
      return val;
    }
}

  1. get方法與set方法類似瞎疼,都是根據(jù)key一次查詢相關(guān)信息科乎,查詢順序?yàn)?code>getKey--->key--->isKey--->_getKey--->_key(方法)--->_key(實(shí)例變量)--->_isKey--->key--->isKey, 前5個查詢?yōu)榉椒ǔ笊鳎?個為實(shí)例變量喜喂。
  2. 同樣根據(jù)accessInstanceVariablesDirectly判斷是否直接操作實(shí)例變量,其中包含_getKey _key方法
  3. 然后根據(jù)獲取到的內(nèi)容調(diào)用GSObjCGetVal()獲取值
  4. GSObjCGetVal()GSObjCSetVal()邏輯相似竿裂,不過用來判斷的是方法返回值的類型進(jìn)行判斷玉吁。同樣,如果類型為空腻异,則調(diào)用valueForUndefinedKey:方法拋出異常进副。
  5. 然后根據(jù)類型、方法/實(shí)例變量進(jìn)行取值悔常,如果是方法影斑,則獲取IMP指針調(diào)用,如果只實(shí)例變量机打,則直接根據(jù)指針取值矫户。
  6. 最后如果類型匹配不到,則調(diào)用valueForUndefinedKey:方法拋出異常残邀。

三皆辽、KeyPath 相關(guān)

KVC中可以使用點(diǎn)語法,進(jìn)行深層次的賦值芥挣、取值驱闷。

- (void) setValue: (id)anObject forKeyPath: (NSString*)aKey
{
  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];
    }
}

- (id) valueForKeyPath: (NSString*)aKey
{
  NSRange       r = [aKey rangeOfString: @"." options: NSLiteralSearch];

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

      return [[self valueForKey: key] valueForKeyPath: path];
    }
}

兩個方法類似,都是利用遞歸逐級調(diào)用 valueForKey: 以及 setValue:forKey:


最后

  1. KVC 就是利用字符串空免,來查詢到和對象相關(guān)聯(lián)的方法空另、實(shí)例變量,來達(dá)到間接訪問屬性方法或成員變量蹋砚。
  2. 由于KVC在訪問屬性方法或成員變量前加了一層查詢機(jī)制扼菠,所以效率沒有直接訪問屬性方法或成員變量高。
  3. 由于KVC的使用是通過字符串, 所以極易出錯坝咐,推薦借鑒 RAC中的 @keyPath宏娇豫, RAC宏分析8:@keypath
  4. 由上面源碼得知,如果key相關(guān)聯(lián)信息找不到畅厢,獲取傳入?yún)?shù)類型不匹配都會導(dǎo)致異常冯痢,所以使用時,最后要重寫相應(yīng)的方法
  5. 容器類包含了很多便捷的keyPath,
    @count
    @avg
    @max
    @min
    @sum
    @distinctUnionOfArrays
    @distinctUnionOfObjects
    @distinctUnionOfSets
    @unionOfArrays
    @unionOfObjects
    @unionOfSets
    以上keyPath都是通過容器里重寫valueForKeyPath:實(shí)現(xiàn),而且還可以通過數(shù)組浦楣,一次性返回內(nèi)部對象的某個屬性集合袖肥。
    由于這些方法都是通過KVC實(shí)現(xiàn),所以效率比較低振劳,而且由于返回值為對象椎组,所以還進(jìn)行了不必要的封裝
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市历恐,隨后出現(xiàn)的幾起案子寸癌,更是在濱河造成了極大的恐慌,老刑警劉巖弱贼,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒸苇,死亡現(xiàn)場離奇詭異,居然都是意外死亡吮旅,警方通過查閱死者的電腦和手機(jī)溪烤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庇勃,“玉大人檬嘀,你說我怎么就攤上這事≡鹑拢” “怎么了鸳兽?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長罕拂。 經(jīng)常有香客問我贸铜,道長,這世上最難降的妖魔是什么聂受? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮烤镐,結(jié)果婚禮上蛋济,老公的妹妹穿的比我還像新娘。我一直安慰自己炮叶,他們只是感情好碗旅,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镜悉,像睡著了一般祟辟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侣肄,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天丈秩,我揣著相機(jī)與錄音柴钻,去河邊找鬼诚卸。 笑死浆劲,一個胖子當(dāng)著我的面吹牛秸苗,可吹牛的內(nèi)容都是我干的怖竭。 我是一名探鬼主播陡蝇,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼广匙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起艇潭,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤戏蔑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后总棵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡情龄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了骤视。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡睹逃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沉填,到底是詐尸還是另有隱情,我是刑警寧澤佑笋,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站猎荠,受9級特大地震影響坚弱,放射性物質(zhì)發(fā)生泄漏史汗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一停撞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧戈毒,春花似錦、人聲如沸埋市。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至污茵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間泞当,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工襟士, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陋桂。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓蝶溶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親身坐。 傳聞我的和親對象是個殘疾皇子落包,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • KVC KVC定義 KVC(Key-value coding)鍵值編碼,就是指iOS的開發(fā)中涯鲁,可以允許開發(fā)者通過K...
    暮年古稀ZC閱讀 2,128評論 2 9
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,321評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • KVC KVC定義 KVC(Key-value coding)鍵值編碼抹腿,就是指iOS的開發(fā)中,可以允許開發(fā)者通過K...
    jackyshan閱讀 51,820評論 9 200
  • 夜深警绩,人靜 是屬于自己的思考時間 越靜,想得越透徹 越想肩祥,越有勇氣面對 深夜,適合做出痛苦的決定 或許混狠,會有一場鬧...
    一朵烏云閱讀 210評論 0 0