KVO valueForKey source code parsing

First of all, I read the source code of GNUBase to understand the process of calling some functions of KVC, and Apple’s own Foundation has some differences.

In the KeyValueCoding file we find the following methods:

- (id) valueForKey: (NSString*)aKey { unsigned size = [aKey length] * 8; Char key[size + 1] char key[size + 1]; [aKey getCString: key maxLength: size + 1 encoding: NSUTF8StringEncoding]; size = strlen(key); // Recalculate the size value. If key is a Chinese string, then size = 3 * akey. length return ValueForKey(self, key, size); }Copy the code

As it happens, we can also see a rough implementation of the getCString method, which is essentially an operation that plugs NSString data into a char array and returns whether it was successful or not.

- (BOOL) getCString: (char*)buffer maxLength: (NSUInteger)maxLength encoding: (NSStringEncoding)encoding { if (0 == maxLength || 0 == buffer) return NO; if (encoding == NSUnicodeStringEncoding) { unsigned length = [self length]; if (maxLength > length * sizeof(unichar)) { unichar *ptr = (unichar*)(void*)buffer; maxLength = (maxLength - 1) / sizeof(unichar); [self getCharacters: ptr range: NSMakeRange(0, maxLength)]; ptr[maxLength] = 0; return YES; } return NO; } else { NSData *d = [self dataUsingEncoding: encoding]; unsigned length = [d length]; BOOL result = (length < maxLength) ? YES : NO; if (d == nil) { [NSException raise: NSCharacterConversionException format: @"Can't convert to C string."]; } if (length >= maxLength) { length = maxLength-1; } memcpy(buffer, [d bytes], length); buffer[length] = '\0'; return result; }}Copy the code

Next let’s look at the implementation of the ValueForKey method:

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; memcpy(buf, "_get", 4); memcpy(&buf[4], key, size); buf[size + 4] = '\0'; buff = _get + key + '\0' lo = buf[4]; hi = islower(lo) ? toupper(lo) : lo; buf[4] = hi; Name = &buf[1]; // getKey sel = sel_getUid(name); / / find a cache of the selector, specific implementation can look down the if (sel = = 0 | | [self respondsToSelector: sel] = = NO) / / if not {buf [4] = lo; Name = &buf[4]; // getKey -> getKey; // 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); }Copy the code

So we’re converting key to getKey,isKey,_isKey,_getKey,key,_key.

Obtain the corresponding type, size, and offset from GSObjCFindVariable

Finally, call GSObjCGetVal to get the value of the case method.

Let’s look at the implementation of the sel_getUid method:

SEL sel_getUid(const char *name) { return __sel_registerName(name, 2, 1); // YES lock, YES copy } static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) { SEL result = 0; if (shouldLock) selLock.assertUnlocked(); else selLock.assertLocked(); if (! name) return (SEL)0; result = search_builtins(name); _dyLD_get_objc_selector if (result) return result; conditional_mutex_locker_t lock(selLock, shouldLock); auto it = namedSelectors.get().insert(name); Second) {// No match. Insert. *it.first = (const char *)sel_alloc(name, copy); } return (SEL)*it.first; } static SEL search_builtins(const char *name) { #if SUPPORT_PREOPT if (SEL result = (SEL)_dyld_get_objc_selector(name))  return result; #endif return nil; }Copy the code