Object-c 反射技术
背景:
App 随着迭代的发展,需要把c++与一些算法进行解耦,这样oc runtime的 反射可以利用,这样当app如果集成了算法组件,通过查找是否有对应class,从而可以动态判断方法是否可用。
在正式代码之前,先看下几个方法对应的含义
objc_getClass
这个是反射的核心,通过一个字符串构查找 类对象
| C++ Class objc_getClass(const char *aClassName) { if (!aClassName) return Nil; // NO unconnected, YES class handler return look_up_class(aClassName, NO, YES); } 。。。。 /*********************************************************************** * look_up_class * Look up a class by name, and realize it. * Locking: acquires runtimeLock **********************************************************************/ static BOOL empty_getClass(const char *name, Class *outClass) { *outClass = nil; return NO; } static ChainedHookFunction GetClassHook{empty_getClass}; void objc_setHook_getClass(objc_hook_getClass newValue, objc_hook_getClass *outOldValue) { GetClassHook.set(newValue, outOldValue); } Class look_up_class(const char *name, bool includeUnconnected __attribute__((unused)), bool includeClassHandler __attribute__((unused))) { if (!name) return nil; Class result; bool unrealized; { runtimeLock.lock(); result = getClassExceptSomeSwift(name); unrealized = result && !result->isRealized(); if (unrealized) { result = realizeClassMaybeSwiftAndUnlock(result, runtimeLock); // runtimeLock is now unlocked } else { runtimeLock.unlock(); } } if (!result) { // Ask Swift about its un-instantiated classes. // We use thread-local storage to prevent infinite recursion // if the hook function provokes another lookup of the same name // (for example, if the hook calls objc_allocateClassPair) auto *tls = _objc_fetch_pthread_data(true); // Stop if this thread is already looking up this name. for (unsigned i = 0; i < tls->classNameLookupsUsed; i++) { if (0 == strcmp(name, tls->classNameLookups[i])) { return nil; } } // Save this lookup in tls. if (tls->classNameLookupsUsed == tls->classNameLookupsAllocated) { tls->classNameLookupsAllocated = (tls->classNameLookupsAllocated * 2 ?: 1); size_t size = tls->classNameLookupsAllocated * sizeof(tls->classNameLookups[0]); tls->classNameLookups = (const char **) realloc(tls->classNameLookups, size); } tls->classNameLookups[tls->classNameLookupsUsed++] = name; // Call the hook. Class swiftcls = nil; if (GetClassHook.get()(name, &swiftcls)) { ASSERT(swiftcls->isRealized()); result = swiftcls; } // Erase the name from tls. unsigned slot = --tls->classNameLookupsUsed; ASSERT(slot >= 0 && slot < tls->classNameLookupsAllocated); ASSERT(name == tls->classNameLookups[slot]); tls->classNameLookups[slot] = nil; } return result; } |
能看的出来 会从tls(Thread Local Storage),classNameLookups,查找对应的class
class_getClassMethod
| C++ OBJC_EXPORT Method _Nullable class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)
。。。。。 NEVER_INLINE IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) { const IMP forward_imp = (IMP)_objc_msgForward_impcache; IMP imp = nil; Class curClass; runtimeLock.assertUnlocked(); if (slowpath(!cls->isInitialized())) { // The first message sent to a class is often +new or +alloc, or +self // which goes through objc_opt_* or various optimized entry points. // // However, the class isn't realized/initialized yet at this point, // and the optimized entry points fall down through objc_msgSend, // which ends up here. // // We really want to avoid caching these, as it can cause IMP caches // to be made with a single entry forever. // // Note that this check is racy as several threads might try to // message a given class for the first time at the same time, // in which case we might cache anyway. behavior |= LOOKUP_NOCACHE; } // runtimeLock is held during isRealized and isInitialized checking // to prevent races against concurrent realization. // runtimeLock is held during method search to make // method-lookup + cache-fill atomic with respect to method addition. // Otherwise, a category could be added but ignored indefinitely because // the cache was re-filled with the old value after the cache flush on // behalf of the category. runtimeLock.lock(); // We don't want people to be able to craft a binary blob that looks like // a class but really isn't one and do a CFI attack. // // To make these harder we want to make sure this is a class that was // either built into the binary or legitimately registered through // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair. checkIsKnownClass(cls); cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE); // runtimeLock may have been dropped but is now locked again runtimeLock.assertLocked(); curClass = cls; // The code used to lookup the class's cache again right after // we take the lock but for the vast majority of the cases // evidence shows this is a miss most of the time, hence a time loss. // // The only codepath calling into this without having performed some // kind of cache lookup is class_getInstanceMethod(). for (unsigned attempts = unreasonableClassCount();;) { if (curClass->cache.isConstantOptimizedCache(/* strict */true)) { #if CONFIG_USE_PREOPT_CACHES imp = cache_getImp(curClass, sel); if (imp) goto done_unlock; curClass = curClass->cache.preoptFallbackClass(); #endif } else { // curClass method list. Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { imp = meth->imp(false); goto done; } if (slowpath((curClass = curClass->getSuperclass()) == nil)) { // No implementation found, and method resolver didn't help. // Use forwarding. imp = forward_imp; break; } } // Halt if there is a cycle in the superclass chain. if (slowpath(--attempts == 0)) { _objc_fatal("Memory corruption in class list."); } // Superclass cache. imp = cache_getImp(curClass, sel); if (slowpath(imp == forward_imp)) { // Found a forward:: entry in a superclass. // Stop searching, but don't cache yet; call method // resolver for this class first. break; } if (fastpath(imp)) { // Found the method in a superclass. Cache it in this class. goto done; } } // No implementation found. Try method resolver once. if (slowpath(behavior & LOOKUP_RESOLVER)) { behavior ^= LOOKUP_RESOLVER; return resolveMethod_locked(inst, sel, cls, behavior); } done: if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) { #if CONFIG_USE_PREOPT_CACHES while (cls->cache.isConstantOptimizedCache(/* strict */true)) { cls = cls->cache.preoptFallbackClass(); } #endif log_and_fill_cache(cls, imp, sel, inst, curClass); } done_unlock: runtimeLock.unlock(); if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) { return nil; } return imp; } |
lookUpImpOrForward 网上有很多资料介绍这个方法,这个是整个反射的核心,通过Class找到对应的方法,具体过程可以参考其他的网上资料,核心无非先从cache找有无方法,没有在从class的方法列表找,还没有可以看下能否进行消息转发等等,最终就是找到这个methed
有了class method 就可以提取方法的函数指针,有了函数指针就可以反射调用了
实例方法反射
| C++ { Class cls = objc_getClass("NSString"); NSObject *pObject = [cls alloc]; Method thmod= class_getInstanceMethod(cls, @selector(init)); IMP funPtr = method_getImplementation(thmod); NSString *pS = ((id(*)(id, SEL))funPtr)(pObject, @selector(init)); } |
Oc 方法转成c 后默认前边都会id, SEL,就像c++类方法,默认会有this指针
类方法反射
| C++ Class metaCls = objc_getMetaClass("NSString"); if (metaCls) { Method thmod = class_getClassMethod(cls, @selector(string)); IMP funPtr = method_getImplementation(thmod); NSString *pS = ((id(*)(id, SEL))funPtr)(cls, @selector(string));
} |
class_getClassMethod
| C++ Method class_getClassMethod(Class cls, SEL sel) { if (!cls || !sel) return nil; return class_getInstanceMethod(cls->getMeta(), sel); } Class getMeta() { if (isMetaClassMaybeUnrealized()) return (Class)this; else return this->ISA(); } |
可以看的出来 如果cls是类对象,则会直接去ISA找到类对象,元类对象直接返回
所以类方法反射的时候可以穿metaClass,也可以Class