探秘Runtime - Runtime加载过程

栏目: C · 发布时间: 4年前

内容简介:在iOS程序中会用到很多系统的动态库,这些动态库都是动态加载的。所有iOS程序共用一套系统动态库,在程序开始运行时才会开始链接动态库。除了在项目设置里显式出现的动态库外,还会有一些隐式存在的动态库。例如

<简书 — 刘小壮> https://www.jianshu.com/p/4fb2d7014e9e

探秘Runtime - Runtime加载过程

程序加载过程

在iOS程序中会用到很多系统的动态库,这些动态库都是动态加载的。所有iOS程序共用一套系统动态库,在程序开始运行时才会开始链接动态库。

探秘Runtime - Runtime加载过程

除了在项目设置里显式出现的动态库外,还会有一些隐式存在的动态库。例如 objcRuntime 所属的 libobjc.dyldlibSystem.dyld ,在 libSystem 中包含常用的 libdispatch(GCD)libsystem_c (C语言基础库)、 libsystem_blocks(Block) 等。

使用动态库的优点:

App

加载过程

在应用程序启动后,由 dyld(the dynamic link editor) 进行程序的初始化操作。大概流程就像下面列出的步骤,其中第3、4、5步会执行多次,在 ImageLoader 加载新的 image 进内存后就会执行一次。

  1. 在引用程序启动后,由 dyld 将应用程序加载到二进制中,并完成一些文件的初始化操作。
  2. Runtimedyld 中注册回调函数。
  3. 通过 ImageLoader 将所有 image 加载到内存中。
  4. dyldimage 发生改变时,主动调用回调函数。
  5. Runtime 接收到 dyld 的函数回调,开始执行 map_imagesload_images 等操作,并回调 +load 方法。
  6. 调用 main() 函数,开始执行业务代码。

ImageLoaderimage 的加载器, image 可以理解为编译后的二进制。

下面是在 Runtimemap_images 函数打断点,观察回调情况的汇编代码。可以看出,调用是由 dyld 发起的,由 ImageLoader 通知 dyld 进行调用。

探秘Runtime - Runtime加载过程

关于 dyld 我并没有深入研究,有兴趣的同学可以到 Github 上下载源码研究一下。

动态加载

一个OC程序可以在运行过程中动态加载和链接新类或 Category ,新类或 Category 会加载到程序中,其处理方式和其他类是相同的。动态加载还可以做许多不同的事,动态加载允许应用程序进行自定义处理。

OC提供了 objc_loadModules 运行时函数,执行 Mach-O 中模块的动态加载,在上层 NSBundle 对象提供了更简单的访问 API

map images

Runtime 加载时,会调用 _objc_init 函数,并在内部注册三个函数指针。其中 map_images 函数是初始化的关键,内部完成了大量 Runtime 环境的初始化操作。

map_images 函数中,内部也是做了一个调用中转。然后调用到 map_images_nolock 函数,内部核心就是 _read_images 函数。

void _objc_init(void)
{
    // .... 各种init
    _dyld_objc_notify_register(↦_images, load_images, unmap_image);
}

void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
}
复制代码

_read_images 函数中完成了大量的初始化操作,函数内部代码量比较大,下面是精简版带注释的源代码。

先整体梳理一遍 _read_images 函数内部的逻辑:

  1. 加载所有类到类的 gdb_objc_realized_classes 表中。
  2. 对所有类做重映射。
  3. 将所有 SEL 都注册到 namedSelectors 表中。
  4. 修复函数指针遗留。
  5. 将所有 Protocol 都添加到 protocol_map 表中。
  6. 对所有 Protocol 做重映射。
  7. 初始化所有非懒加载的类,进行 rwro 等操作。
  8. 遍历已标记的懒加载的类,并做初始化操作。
  9. 处理所有 Category ,包括 ClassMeta Class
  10. 初始化所有未初始化的类。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    TimeLogger ts(PrintImageTimes);

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    if (!doneOnce) {
        doneOnce = YES;
        // 实例化存储类的哈希表,并且根据当前类数量做动态扩容
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
    }

    // 由编译器读取类列表,并将所有类添加到类的哈希表中,并且标记懒加载的类并初始化内存空间
    for (EACH_HEADER) {
        if (! mustReadClasses(hi)) {
            continue;
        }

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();

        /** 将新类添加到哈希表中 */
        
        // 从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        for (i = 0; i < count; i++) {
            // 数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。以及自己创建的类
            Class cls = (Class)classlist[i];
            // 通过readClass函数获取处理后的新类,内部主要操作ro和rw结构体
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            // 初始化所有懒加载的类需要的内存空间
            if (newCls != cls  &&  newCls) {
                // 将懒加载的类添加到数组中
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
    
    // 将未映射Class和Super Class重映射,被remap的类都是非懒加载的类
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            // 重映射Class,注意是从_getObjc2ClassRefs函数中取出类的引用
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // 重映射父类
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

    // 将所有SEL都注册到哈希表中,是另外一张哈希表
    static size_t UnfixedSelectors;
    sel_lock();
    for (EACH_HEADER) {
        if (hi->isPreoptimized()) continue;

        bool isBundle = hi->isBundle();
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            // 注册SEL的操作
            sels[i] = sel_registerNameNoLock(name, isBundle);
        }
    }

    // 修复旧的函数指针调用遗留
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;
        for (i = 0; i < count; i++) {
            // 内部将常用的alloc、objc_msgSend等函数指针进行注册,并fix为新的函数指针
            fixupMessageRef(refs+i);
        }
    }

    // 遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        // cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        // 获取protocol哈希表
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

        // 从编译器中读取并初始化Protocol
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
    
    // 修复协议列表引用,优化后的images可能是正确的,但是并不确定
    for (EACH_HEADER) {
        // 需要注意到是,下面的函数是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不一样
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

    // 实现非懒加载的类,对于load方法和静态实例变量
    for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            // 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
            realizeClass(cls);
        }
    }

    // 遍历resolvedFutureClasses数组,实现所有懒加载的类
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            // 实现懒加载的类
            realizeClass(resolvedFutureClasses[i]);
            resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    // 发现和处理所有Category
    for (EACH_HEADER) {
        // 外部循环遍历找到当前类,查找类对应的Category数组
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        // 内部循环遍历当前类的所有Category
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            // 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                // 将Category添加到对应Class的value中,value是Class对应的所有category数组
                addUnattachedCategoryForClass(cat, cls, hi);
                // 将Category的method、protocol、property添加到Class
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

            // 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
            // 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
            }
        }
    }

    // 初始化从磁盘中加载的所有类,发现Category必须是最后执行的
    // 从runtime objc4-532版本源码来看,DebugNonFragileIvars字段一直是-1,所以不会进入这个方法中
    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }
#undef EACH_HEADER
}
复制代码

其内部还调用了很多其他函数,后面会详细介绍函数内部实现。

load images

在项目中经常用到 load 类方法, load 类方法的调用时机比 main 函数还要靠前。 load 方法是由系统来调用的,并且在整个程序运行期间,只会调用一次,所以可以在 load 方法中执行一些只执行一次的操作。

一般 Method Swizzling 都会放在 load 方法中执行,这样在执行 main 函数前,就可以对类方法进行交换。可以确保正式执行代码时,方法肯定是被交换过的。

如果对一个类添加Category后,并且重写其原有方法,这样会导致Category中的方法覆盖原类的方法。但是load方法却是例外,所有Category和原类的load方法都会被执行。

源码分析

load 方法由 Runtime 进行调用,下面我们分析一下 load 方法的实现, load 的实现源码都在 objc-loadmethod.mm 中。

在一个新的工程中,我们创建一个 TestObject 类,并在其 load 方法中打一个断点,看一下系统的调用堆栈。

探秘Runtime - Runtime加载过程

从调用栈可以看出,是通过系统的动态链接器 dyld 开始的调用,然后调到 Runtimeload_images 函数中。 load_images 函数是通过 _dyld_objc_notify_register 函数,将自己的函数指针注册给 dyld 的。

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(↦_images, load_images, unmap_image);
}
复制代码

load_images 函数中主要做了两件事,首先通过 prepare_load_methods 函数准备 Class load listCategory load list ,然后通过 call_load_methods 函数调用已经准备好的两个方法列表。

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!hasLoadMethods((const headerType *)mh)) return;
    prepare_load_methods((const headerType *)mh);
    call_load_methods();
}
复制代码

首先我们看一下 prepare_load_methods 函数的实现,看一下其内部是怎么查找 load 方法的。可以看到,其内部主要分为两部分,查找 Classload 方法列表和查找 Category 方法列表。

准备类的方法列表时,首先通过 _getObjc2NonlazyClassList 函数获取所有非懒加载类的列表,这时候获取到的是一个 classref_t 类型的数组,然后遍历数组添加 load 方法列表。

Category 过程也是类似,通过 _getObjc2NonlazyCategoryList 函数获取所有非懒加载 Category 的列表,得到一个 category_t 类型的数组,需要注意的是这是一个指向指针的指针。然后对其进行遍历并添加到 load 方法列表, ClassCategoryload 方法列表是两个列表。

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    // 获取到非懒加载的类的列表
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 设置Class的调用列表
        schedule_class_load(remapClass(classlist[i]));
    }
    
    // 获取到非懒加载的Category列表
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        // 忽略弱链接的类别
        if (!cls) continue;
        // 实例化所属的类
        realizeClass(cls);
        // 设置Category的调用列表
        add_category_to_loadable_list(cat);
    }
}
复制代码

在添加类的 load 方法列表时,内部会递归遍历把所有父类的 load 方法都添加进去,顺着继承者链的顺序添加,会先把父类添加在前面。然后会调用 add_class_to_loadable_list 函数,将自己的 load 方法添加到对应的数组中。

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    // 已经添加Class的load方法到调用列表中
    if (cls->data()->flags & RW_LOADED) return;

    // 确保super已经被添加到load列表中,默认是整个继承者链的顺序
    schedule_class_load(cls->superclass);
    
    // 将IMP和Class添加到调用列表
    add_class_to_loadable_list(cls);
    // 设置Class的flags,表示已经添加Class到调用列表中
    cls->setInfo(RW_LOADED); 
}
复制代码

Category 则不需要考虑父类的问题,所以直接在 prepare_load_methods 函数中遍历 Category 数组,然后调用 add_category_to_loadable_list 函数即可。

add_category_to_loadable_list 函数中,会判断当前 Category 有没有实现 load 方法,如果没有则直接 return ,如果实现了则添加到 loadable_categories 数组中。

类的 add_class_to_loadable_list 函数内部实现也是类似,区别在于类的数组叫做 loadable_classes

void add_category_to_loadable_list(Category cat)
{
    IMP method;

    // 获取Category的load方法的IMP
    method = _category_getLoadMethod(cat);

    // 如果Category没有load方法则return
    if (!method) return;
    // 如果已使用大小等于数组大小,对数组进行动态扩容
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }

    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}
复制代码

到此为止, loadable_classesloadable_categories 两个数组已经准备好了, load_images 会调用 call_load_methods 函数执行这些 load 方法。在这个方法中, call_class_loads 函数是负责调用类方法列表的, call_category_loads 负责调用 Category 的方法列表。

void call_load_methods(void)
{
    bool more_categories;
    void *pool = objc_autoreleasePoolPush();

    do {
        // 反复执行call_class_loads函数,直到数组中没有可执行的load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        more_categories = call_category_loads();
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);
    loading = NO;
}
复制代码

下面是调用类方法列表的代码,内部主要是通过对 loadable_classes 数组进行遍历,并获取到 loadable_class 的结构体,结构体中存在 ClassIMP ,然后直接调用即可。

Category 的调用方式和类的一样,就不在下面贴代码了。需要注意的是, load 方法都是直接调用的,并没有走运行时的 objc_msgSend 函数。

static void call_class_loads(void)
{
    int i;
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue; 
        (*load_method)(cls, SEL_load);
    }
    
    if (classes) free(classes);
}

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};
复制代码

根据上面的源码分析,我们可以看出 load 方法的调用顺序应该是 “父类 -> 子类 -> 分类” 的顺序。因为执行加载 Class 的时机是在 Category 之前的,而且 load 子类之前会先 load 父类,所以是这种顺序。

initialize

load 方法类似的也有 initialize 方法, initialize 方法也是由 Runtime 进行调用的,自己不可以直接调用。与 load 方法不同的是, initialize 方法是在第一次调用类所属的方法时,才会调用 initialize 方法,而 load 方法是在 main 函数之前就全部调用了。所以理论上来说 initialize 可能永远都不会执行,如果当前类的方法永远不被调用的话。

下面我们研究一下 initializeRuntime 中的源码。

在向对象发送消息时, lookUpImpOrForward 函数中会判断当前类是否被初始化,如果没有被初始化,则先进行初始化再调用类的方法。

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);

// ....省略好多代码

// 第一次调用当前类的话,执行initialize的代码
if (initialize  &&  !cls->isInitialized()) {
    _class_initialize (_class_getNonMetaClass(cls, inst));
}

// ....省略好多代码
复制代码

在进行初始化的时候,和 load 方法的调用顺序一样,会按照继承者链先初始化父类。 _class_initialize 函数中关键的两行代码是 callInitializelockAndFinishInitializing 的调用。

// 第一次调用类的方法,初始化类对象
void _class_initialize(Class cls)
{
    Class supercls;
    bool reallyInitialize = NO;

    // 递归初始化父类。initizlize不用显式的调用super,因为runtime已经在内部调用了
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    if (reallyInitialize) {
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            performForkChildInitialize(cls, supercls);
            return;
        }
        @try {
            // 通过objc_msgSend()函数调用initialize方法
            callInitialize(cls);
        }
        @catch (...) {
            @throw;
        }
        @finally {
            // 执行initialize方法后,进行系统的initialize过程
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            return;
        } else {
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
}
复制代码

通过 objc_msgSend 函数调用 initialize 方法。

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
复制代码

lockAndFinishInitializing 函数中会完成一些初始化操作,其内部会调用 _finishInitializing 函数,在函数内部会调用 classsetInitialized 函数,核心工作都由 setInitialized 函数完成。

static void lockAndFinishInitializing(Class cls, Class supercls)
{
    monitor_locker_t lock(classInitLock);
    if (!supercls  ||  supercls->isInitialized()) {
        _finishInitializing(cls, supercls);
    } else {
        _finishInitializingAfter(cls, supercls);
    }
}
复制代码

负责初始化类和元类,函数内部主要是查找当前类和元类中是否定义了某些方法,然后根据查找结果设置类和元类的一些标志位。

void 
objc_class::setInitialized()
{
    Class metacls;
    Class cls;

    // 获取类和元类对象
    cls = (Class)this;
    metacls = cls->ISA();

    bool inherited;
    bool metaCustomAWZ = NO;
    if (MetaclassNSObjectAWZSwizzled) {
        metaCustomAWZ = YES;
        inherited = NO;
    }
    else if (metacls == classNSObject()->ISA()) {
        // 查找是否实现了alloc和allocWithZone方法
        auto& methods = metacls->data()->methods;
        for (auto mlists = methods.beginCategoryMethodLists(), 
                  end = methods.endCategoryMethodLists(metacls); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsAWZ(*mlists)) {
                metaCustomAWZ = YES;
                inherited = NO;
                break;
            }
        }
    }
    else if (metacls->superclass->hasCustomAWZ()) {
        metaCustomAWZ = YES;
        inherited = YES;
    } 
    else {
        auto& methods = metacls->data()->methods;
        for (auto mlists = methods.beginLists(),
                  end = methods.endLists(); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsAWZ(*mlists)) {
                metaCustomAWZ = YES;
                inherited = NO;
                break;
            }
        }
    }
    if (!metaCustomAWZ) metacls->setHasDefaultAWZ();
    if (PrintCustomAWZ  &&  metaCustomAWZ) metacls->printCustomAWZ(inherited);

    bool clsCustomRR = NO;
    if (ClassNSObjectRRSwizzled) {
        clsCustomRR = YES;
        inherited = NO;
    }
    // 查找元类是否实现MRC方法,如果是则进入if语句中
    if (cls == classNSObject()) {
        auto& methods = cls->data()->methods;
        for (auto mlists = methods.beginCategoryMethodLists(), 
                  end = methods.endCategoryMethodLists(cls); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsRR(*mlists)) {
                clsCustomRR = YES;
                inherited = NO;
                break;
            }
        }
    }
    else if (!cls->superclass) {
        clsCustomRR = YES;
        inherited = NO;
    } 
    else if (cls->superclass->hasCustomRR()) {
        clsCustomRR = YES;
        inherited = YES;
    } 
    else {
        // 查找类是否实现MRC方法,如果是则进入if语句中
        auto& methods = cls->data()->methods;
        for (auto mlists = methods.beginLists(), 
                  end = methods.endLists(); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsRR(*mlists)) {
                clsCustomRR = YES;
                inherited = NO;
                break;
            }
        }
    }
    if (!clsCustomRR) cls->setHasDefaultRR();
    if (PrintCustomRR  &&  clsCustomRR) cls->printCustomRR(inherited);
    metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING);
}
复制代码

需要注意的是, initialize 方法和 load 方法不太一样, Category 中定义的 initialize 方法会覆盖原方法而不是像 load 方法一样都可以调用。

简书由于排版的问题,阅读体验并不好,布局、图片显示、代码等很多问题。所以建议到我 Github 上,下载 Runtime PDF 合集。把所有 Runtime 文章总计九篇,都写在这个 PDF 中,而且左侧有目录,方便阅读。

探秘Runtime - Runtime加载过程

下载地址: Runtime PDF 麻烦各位大佬点个赞,谢谢!:grin:


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python 3网络爬虫开发实战

Python 3网络爬虫开发实战

崔庆才 / 人民邮电出版社 / 2018-4 / 99

本书介绍了如何利用Python 3开发网络爬虫,书中首先介绍了环境配置和基础知识,然后讨论了urllib、requests、正则表达式、Beautiful Soup、XPath、pyquery、数据存储、Ajax数据爬取等内容,接着通过多个案例介绍了不同场景下如何实现数据爬取,后介绍了pyspider框架、Scrapy框架和分布式爬虫。 本书适合Python程序员阅读。一起来看看 《Python 3网络爬虫开发实战》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

html转js在线工具
html转js在线工具

html转js在线工具