• Object-c 反射技术


    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, NOYES);
    }
     
     。。。。
     
     
    /***********************************************************************
    * 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(*)(idSEL))funPtr)(pObject, @selector(init));
                
      }

    Oc 方法转成c 后默认前边都会idSEL,就像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(*)(idSEL))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

  • 相关阅读:
    MySQL 安装详细步骤
    自动化测试Mock神器:轻松模拟HTTP请求..
    健康防猝指南2:饮食健康
    C#处理医学影像(三):基于漫水边界自动选取病灶范围的实现思路
    redis-springboot、分布式锁
    模拟电路 第二章(三极管及其放大电路)【上】
    腾讯云服务器多少钱一年?2023年腾讯云优惠云服务器推荐
    Kubernetes 基本概念
    L1-030 一帮一 C++解法
    操作系统笔记——Linux实战、Windows(持续更新)
  • 原文地址:https://blog.csdn.net/c553110519/article/details/126568622