KVC 原理淺析學習<1>

什么是KVC沐序?

KVC(Key-value coding)鍵值編碼,就是指iOS的開發(fā)中,可以允許開發(fā)者通過Key名直接訪問對象的屬性溯泣,或者給對象的屬性賦值虐秋。而不需要調(diào)用明確的存取方法。這樣就可以在運行時動態(tài)地訪問和修改對象的屬性垃沦。而不是在編譯時確定客给,這也是iOS開發(fā)中的黑魔法之一。很多高級的iOS開發(fā)技巧都是基于KVC實現(xiàn)的肢簿。

通過一個例子?? 來學習一下kvc的一般賦值過程吧
1: 通常情況下靶剑,我們會這么使用:

    Person *person = [[Person alloc] init];
    person.name   = @"小明"; 
    NSLog(@"%@ - %d - %@",person.name);
打印就會出來:我們剛剛給person賦的值
控制臺打印結(jié)果:[6209:645158]小明

好像一直忽視了這個值是怎么賦值到person.name上的。
那在底層實際上是llvm { https://github.com/llvm/llvm-project/releases/tag/llvmorg-9.0.1}編譯器通過一系列的操作來給我們的屬性賦值
打開我們的源碼工程objc 跟蹤就會發(fā)現(xiàn)實際會調(diào)用這樣的一個方法:

查看llvm的源碼會發(fā)現(xiàn)這個地方的操作:

  llvm::FunctionCallee getOptimizedSetPropertyFn(bool atomic, bool copy) {
    CodeGen::CodeGenTypes &Types = CGM.getTypes();
    ASTContext &Ctx = CGM.getContext();
    // void objc_setProperty_atomic(id self, SEL _cmd,
    //                              id newValue, ptrdiff_t offset);
    // void objc_setProperty_nonatomic(id self, SEL _cmd,
    //                                 id newValue, ptrdiff_t offset);
    // void objc_setProperty_atomic_copy(id self, SEL _cmd,
    //                                   id newValue, ptrdiff_t offset);
    // void objc_setProperty_nonatomic_copy(id self, SEL _cmd,
    //                                      id newValue, ptrdiff_t offset);

    SmallVector<CanQualType,4> Params;
    CanQualType IdType = Ctx.getCanonicalParamType(Ctx.getObjCIdType());
    CanQualType SelType = Ctx.getCanonicalParamType(Ctx.getObjCSelType());
    Params.push_back(IdType);
    Params.push_back(SelType);
    Params.push_back(IdType);
    Params.push_back(Ctx.getPointerDiffType()->getCanonicalTypeUnqualified());
    llvm::FunctionType *FTy =
        Types.GetFunctionType(
          Types.arrangeBuiltinFunctionDeclaration(Ctx.VoidTy, Params));
    const char *name;
    if (atomic && copy)
      name = "objc_setProperty_atomic_copy";
    else if (atomic && !copy)
      name = "objc_setProperty_atomic";
    else if (!atomic && copy)
      name = "objc_setProperty_nonatomic_copy";
    else
      name = "objc_setProperty_nonatomic";

    return CGM.CreateRuntimeFunction(FTy, name);
  }

使用 debug workflow - Always show Disassembly 查看發(fā)現(xiàn)是進入到這里
libobjc.A.dylib`::objc_setProperty_nonatomic_copy(id, SEL, id, ptrdiff_t):
    0x1003bb220 <+0>:   pushq  %rbp
    0x1003bb221 <+1>:   movq   %rsp, %rbp
    0x1003bb224 <+4>:   subq   $0x70, %rsp
    0x1003bb228 <+8>:   movq   %rdi, -0x48(%rbp)
    0x1003bb22c <+12>:  movq   %rsi, -0x50(%rbp)
    0x1003bb230 <+16>:  movq   %rdx, -0x58(%rbp)
    0x1003bb234 <+20>:  movq   %rcx, -0x60(%rbp)
->  0x1003bb238 <+24>:  movq   -0x48(%rbp), %rcx
    0x1003bb23c <+28>:  movq   -0x50(%rbp), %rdx
    0x1003bb240 <+32>:  movq   -0x58(%rbp), %rsi
    0x1003bb244 <+36>:  movq   -0x60(%rbp), %rdi
池充。抬虽。。纵菌。阐污。。

全局搜索這個方法就會發(fā)現(xiàn)實現(xiàn)跟聲明:從名字我們也能猜到這個是對屬性的賦值:
OBJC_EXPORT void
objc_setProperty_nonatomic_copy(id _Nullable self, SEL _Nonnull _cmd,
                                id _Nullable newValue, ptrdiff_t offset)
    OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0);

void objc_setProperty_nonatomic_copy(id self, SEL _cmd, id newValue, ptrdiff_t offset)
{
    reallySetProperty(self, _cmd, newValue, offset, false, true, false);
}

但是為甚不是直接調(diào)用諸如:setName 這樣直接的方法去設(shè)置屬性咱圆,還要這么麻煩的設(shè)置這些呢笛辟?這么寫有什么好處呢?
譬如:直接在底層生成這個一個setName 的方法 給屬性進行賦值了序苏,但是我們的工程里面可不單單是一個name的屬性 有成百上千的屬性進行賦值的 手幢,那這樣的寫的話豈不是要累死。
這樣寫其實是一種非常好的普世寫法忱详,通用性可以大大增加普適性也更靈活围来。

查看源碼的話會發(fā)現(xiàn)還有好幾個這種針對行的聲明:

OBJC_EXPORT void
objc_setProperty_atomic(id _Nullable self, SEL _Nonnull _cmd,
                        id _Nullable newValue, ptrdiff_t offset)
    OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
objc_setProperty_nonatomic(id _Nullable self, SEL _Nonnull _cmd,
                           id _Nullable newValue, ptrdiff_t offset)
    OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
objc_setProperty_atomic_copy(id _Nullable self, SEL _Nonnull _cmd,
                             id _Nullable newValue, ptrdiff_t offset)
    OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0);

OBJC_EXPORT void
objc_setProperty_nonatomic_copy(id _Nullable self, SEL _Nonnull _cmd,
                                id _Nullable newValue, ptrdiff_t offset)
    OBJC_AVAILABLE(10.8, 6.0, 9.0, 1.0, 2.0);

最終會調(diào)用這個方法:完成對屬性的賦值操作

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

綜上所述:我們的點語法賦值的時候 底層的llvm 會調(diào)用llvm::FunctionCallee getOptimizedSetPropertyFn(bool atomic, bool copy)這個方法對要進行賦值的屬性進行判斷處理 最后返回一個functiontype 和name return CGM.CreateRuntimeFunction(FTy, name);
然后到達 命中name -> objc_setProperty_nonatomic_copy 找到方法objc_setProperty_nonatomic_copy(id _Nullable self, SEL _Nonnull _cmd, id _Nullable newValue, ptrdiff_t offset) 的拓展的通用函數(shù)入口 最后 通過 reallySetProperty(self, _cmd, newValue, offset, false, true, false); 的方法 對屬性進行賦值。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末匈睁,一起剝皮案震驚了整個濱河市监透,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌航唆,老刑警劉巖胀蛮,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異糯钙,居然都是意外死亡粪狼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門任岸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來再榄,“玉大人,你說我怎么就攤上這事享潜±福” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵米碰,是天一觀的道長窝革。 經(jīng)常有香客問我,道長吕座,這世上最難降的妖魔是什么虐译? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮吴趴,結(jié)果婚禮上漆诽,老公的妹妹穿的比我還像新娘。我一直安慰自己锣枝,他們只是感情好厢拭,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撇叁,像睡著了一般供鸠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陨闹,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天楞捂,我揣著相機與錄音,去河邊找鬼趋厉。 笑死寨闹,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的君账。 我是一名探鬼主播繁堡,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乡数!你這毒婦竟也來了椭蹄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤净赴,失蹤者是張志新(化名)和其女友劉穎塑娇,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劫侧,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡埋酬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了烧栋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片写妥。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖审姓,靈堂內(nèi)的尸體忽然破棺而出珍特,到底是詐尸還是另有隱情,我是刑警寧澤魔吐,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布扎筒,位于F島的核電站莱找,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏嗜桌。R本人自食惡果不足惜奥溺,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望骨宠。 院中可真熱鬧浮定,春花似錦、人聲如沸层亿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匿又。三九已至方灾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間碌更,已是汗流浹背迎吵。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留针贬,地道東北人击费。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像桦他,于是被迫代替她去往敵國和親蔫巩。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355