什么是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);
的方法 對屬性進行賦值。