iOS mapImage与loadImage底层探索

dyld的源码
苹果官方资源opensource
objc4-838可编译联调源码

前言

上一章节了解过编译、程序启动和dyld工作流程。在libSystem动态库被加载的时候,它的子libObjc会调用_objc_init进行objc的初始化。

本章节研究:
1._objc_init函数做了什么事
2.map_images -> _read_images
3.类的初始化
4.分类的初始化
5.rwe在什么情况下产生
7.加载分类时,ro和rwe的变化因素
8.load_images
9.类的+load方法的加载和调用顺序
10.unmap_image

一、_objc_init

打开objc4源码,找到_objc_init

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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(); // 运行C++静态构造函数
    runtime_init(); // 初始化 unattachedCategories、allocatedClasses 两张表
    exception_init(); // objc异常处理系统的初始化
#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init(); // Mac OS的处理,与iPhone OS无关

    // 调用_dyld_objc_notify_register是把map_images、load_images、unmap_image三个函数传递进dyld
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true; // 标记已经调用_dyld_objc_notify_register函数
#endif
}
1.environ_init() 环境变量初始化

代码就不粘贴了。大致逻辑就是通过循环读取工程上设置的环境变量。

Edit Scheme上添加Environment Variable环境变量参数:OBJC_HELPOBJC_PRINT_OPTIONS

运行程序后,控制台打印出环境变量说明

举例:如果我们不需要默认优化isa,就在Environment Variable添加参数OBJC_DISABLE_NONPOINTER_ISA为YES

没有开启指针优化
默认开启指针优化

附上环境变量说明:

环境变量说明.png

2.tls_init() 创建线程的析构函数

tls概念: 线程局部存储。每个线程系统都会为其分配私有空间,能存储当前线程的数据。

tls_init()的声明:

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

_objc_pthread_destroyspecific: 对象每线程数据的析构函数

/***********************************************************************
* _objc_pthread_destroyspecific
* Destructor for objc's per-thread data.
* arg shouldn't be NULL, but we check anyway.
**********************************************************************/
extern void _destroyInitializingClassList(struct _objc_initializing_classes *list);
void _objc_pthread_destroyspecific(void *arg)
{
    _objc_pthread_data *data = (_objc_pthread_data *)arg;
    if (data != NULL) {
        _destroyInitializingClassList(data->initializingClasses);
        _destroySyncCache(data->syncCache);
        _destroyAltHandlerList(data->handlerList);
        for (int i = 0; i < (int)countof(data->printableNames); i++) {
            if (data->printableNames[i]) {
                free(data->printableNames[i]);  
            }
        }
        free(data->classNameLookups);

        // add further cleanup here...

        free(data);
    }
}

3.static_init() 运行C++静态构造函数

静态构造函数的作用:初始化静态数据,执行一些仅需要执行一次的特定操作。
在_objc_init()之前,调用C++静态构造函数

/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}

这样的C++构造函数

__attribute__((constructor)) static void function() {
    printf("function \n");
}

自己写案例:

在本类上写一个C++构造函数,它自动调用在load方法之后。

4.runtime_init()

初始化 unattachedCategories、allocatedClasses 两张表

void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
5.exception_init() 初始化libobjc的异常处理系统。
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

数组越界、方法找不到等等相关报错都是在这里初始化的。

6.cache_t::init() 缓存的初始化
void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;

    while (objc_restartableRanges[count].location) {
        count++;
    }

    kr = task_restartable_ranges_register(mach_task_self(),
                                          objc_restartableRanges, count);
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}
7._dyld_objc_notify_register(&map_images, load_images, unmap_image);

是把map_imagesload_imagesunmap_image三个函数传递进dyld。

dyld 941.5的源码 _dyld_objc_notify_register声明:

void APIs::_dyld_objc_notify_register(_dyld_objc_notify_mapped   mapped,
                                      _dyld_objc_notify_init     init,
                                      _dyld_objc_notify_unmapped unmapped)
{
    if ( config.log.apis )
        log("_dyld_objc_notify_register(%p, %p, %p)\n", mapped, init, unmapped);
    setObjCNotifiers(mapped, init, unmapped);

    // If we have prebuilt loaders, then the objc optimisations may hide duplicate classes from libobjc.
    // We need to print the same warnings libobjc would have.
    if ( const PrebuiltLoaderSet* mainSet = this->processPrebuiltLoaderSet() )
        mainSet->logDuplicateObjCClasses(*this);
}

setObjCNotifiers的声明:

void RuntimeState::setObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped)
{
    // 分别保存了这三个函数
    _notifyObjCMapped   = mapped;
    _notifyObjCInit     = init;
    _notifyObjCUnmapped = unmapped;

    withLoadersReadLock(^{
        // callback about already loaded images
        size_t maxCount = this->loaded.size();
        STACK_ALLOC_ARRAY(const mach_header*, mhs, maxCount);
        STACK_ALLOC_ARRAY(const char*, paths, maxCount);
        for ( const Loader* ldr : loaded ) {
            // don't need _mutex here because this is called when process is still single threaded
            const MachOLoaded* ml = ldr->loadAddress(*this);
            if ( ldr->hasObjC ) {
                paths.push_back(ldr->path());
                mhs.push_back(ml);
            }
        }
        if ( !mhs.empty() ) {
            (*_notifyObjCMapped)((uint32_t)mhs.count(), &paths[0], &mhs[0]);
            if ( this->config.log.notifications ) {
                this->log("objc-mapped-notifier called with %ld images:\n", mhs.count());
                for ( uintptr_t i = 0; i < mhs.count(); ++i ) {
                    this->log(" objc-mapped: %p %s\n", mhs[i], paths[i]);
                }
            }
        }
    });
}

保存了这三个函数,在什么时候调用的呢?下面会说的。
map_imagesload_images本章节研究重点。

二、map_images

1. map_images的调用时机

dyld 941.5的源码:start -> prepare -> state.runAllInitializersForMain()

libSystem动态库被加载的时候,它的子libObjc会调用_objc_init进行objc的初始化。

_objc_init() -> _dyld_objc_notify_register

2.map_images做了什么

了解小知识:计算机程序使用的是虚拟内存,通过ASLR的方式使得在程序每次启动的时候,它的地址都是不固定的,所以需要dyld通过rebasebingding两部操作去fix up修复image中的资源指针。

map_images的源码:

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

处理由dyld映射的给定image

map_images_nolock的声明:

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;

    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
        preopt_init(); // 关于共享缓存优化的处理
    }

    if (PrintImages) { // PrintImages环境变量 OBJC_PRINT_IMAGES
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }


    // Find all images with Objective-C metadata.
    hCount = 0;

    // 从Mach-O的Header配置中统计所有的类(可执行文件里的类/动态库里的类等等)
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable's size
#if __OBJC2__
                // If dyld3 optimized the main executable, then there shouldn't
                // be any selrefs needed in the dynamic map so we can just init
                // to a 0 sized map
                if ( !hi->hasPreoptimizedSelectors() ) {
                  size_t count;
                  _getObjc2SelectorRefs(hi, &count);
                  selrefCount += count;
                  _getObjc2MessageRefs(hi, &count);
                  selrefCount += count;
                }
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
#if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }

    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later.
    if (firstTime) {
        sel_init(selrefCount); // 初始化内部使用的selector tables和寄存器选择器。
        arr_init(); // 自动释放池、散列表、关联对象等的初始化,开始扫描weak table

#if SUPPORT_GC_COMPAT
        // Reject any GC images linked to the main executable.
        // We already rejected the app itself above.
        // Images loaded after launch will be rejected by dyld.

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
#endif

#if TARGET_OS_OSX
        // Disable +initialize fork safety if the app is too old (< 10.13).
        // Disable +initialize fork safety if the app has a
        //   __DATA,__objc_fork_ok section.

//        if (!dyld_program_sdk_at_least(dyld_platform_version_macOS_10_13)) {
//            DisableInitializeForkSafety = true;
//            if (PrintInitializing) {
//                _objc_inform("INITIALIZE: disabling +initialize fork "
//                             "safety enforcement because the app is "
//                             "too old.)");
//            }
//        }

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
#endif

    }

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // 在一切设置完成后调用image load funcs。
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

执行所有类的注册和修复(或延迟发现丢失的超类等)

  • 1.关于共享缓存优化处理
  • 2.从Mach-Oheader配置中统计所有的类
  • 3.初始化内部使用的selector tables寄存器选择器
  • 4.自动释放池散列表关联对象等的初始化,开始扫描weak table
  • 5._read_images 内容很多在第3点探究
  • 6.完成上面的操作后调用 image load funcs
3._read_images做了什么

_read_images所做的每个小结都会通过ts.log打印出来的。同时这里面有很多fix up的操作修复image中的资源指针。

_read_images的源码:

/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked 
* list beginning with headerList. 
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
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;
    bool launchTime = NO;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertLocked();

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

    // if只会执行一次,对一些内存的优化(isa优化、小对象优化等等)
    if (!doneOnce) {
        doneOnce = YES;
        launchTime = YES;

#if SUPPORT_NONPOINTER_ISA
        // Disable non-pointer isa under some conditions.

# if SUPPORT_INDEXED_ISA
        // 如果任何image包含旧的Swift3.0之前代码,就不进行isa优化
        for (EACH_HEADER) {
            if (hi->info()->containsSwift()  &&
                hi->info()->swiftUnstableVersion() < objc_image_info::SwiftVersion3)
            {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app or a framework contains Swift code "
                                 "older than Swift 3.0");
                }
                break;
            }
        }
# endif

# if TARGET_OS_OSX // Mac os的
        // Disable non-pointer isa if the app is too old
        // (linked before OS X 10.11)
        // Note: we must check for macOS, because Catalyst and Almond apps
        // return false for a Mac SDK check! rdar://78225780
//        if (dyld_get_active_platform() == PLATFORM_MACOS && !dyld_program_sdk_at_least(dyld_platform_version_macOS_10_11)) {
//            DisableNonpointerIsa = true;
//            if (PrintRawIsa) {
//                _objc_inform("RAW ISA: disabling non-pointer isa because "
//                             "the app is too old.");
//            }
//        }

        // Disable non-pointer isa if the app has a __DATA,__objc_rawisa section
        // New apps that load old extensions may need this.
        for (EACH_HEADER) {
            if (hi->mhdr()->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
                DisableNonpointerIsa = true;
                if (PrintRawIsa) {
                    _objc_inform("RAW ISA: disabling non-pointer isa because "
                                 "the app has a __DATA,__objc_rawisa section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
# endif

#endif
        // 如果环境变量设置了关闭小对象优化,比如NSNumber类型的
        if (DisableTaggedPointers) {
            disableTaggedPointers();
        }
        
        initializeTaggedPointerObfuscator();

        if (PrintConnecting) {
            _objc_inform("CLASS: found %d classes during launch", totalClasses);
        }

        // namedClasses
        // 这个表gdb_objc_realized_classes中不包含预先优化的类。
        // 4/3 is NXMapTable's load factor
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

        ts.log("IMAGE TIMES: first time tasks");
    }

    // Fix up @selector references
    // Note this has to be before anyone uses a method list, as relative method
    // lists point to selRefs, and assume they are already fixed up (uniqued).
    // 修复 @selector 引用。
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) 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 sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel; // rebase:把sels里的元素赋值成正确的内存地址
                }
            }
        }
    }

    ts.log("IMAGE TIMES: fix up selector references");

    // 发现类。修复未解决的未来类。马克包类。
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

    for (EACH_HEADER) {
        if (! mustReadClasses(hi, hasDyldRoots)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        classref_t const *classlist = _getObjc2ClassList(hi, &count);

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

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            if (newCls != cls  &&  newCls) {
                // Class was moved but not deleted. Currently this occurs 
                // only when the new class resolved a future class.
                // Non-lazily realize the class below.
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }

    ts.log("IMAGE TIMES: discover classes");

    // 修复重映射的类
    // 类列表和非惰性类列表保持未映射状态。
    // 类引用和超级引用被重新映射用于消息调度。
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // 修复旧的objc_msgSend_fixup调用站点
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

    // 发现协议。修复协议refs。
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        ASSERT(cls);
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->hasPreoptimizedProtocols();

        // Skip reading protocols if this is an image from the shared cache
        // and we support roots
        // Note, after launch we do need to walk the protocol as the protocol
        // in the shared cache is marked with isCanonical() and that may not
        // be true if some non-shared cache binary was chosen as the canonical
        // definition
        if (launchTime && isPreoptimized) {
            if (PrintProtocols) {
                _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                             hi->fname());
            }
            continue;
        }

        bool isBundle = hi->isBundle();

        protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }

    ts.log("IMAGE TIMES: discover protocols");

    // 修复 @protocol 引用
    // 预先优化的image可能已经有了正确的答案,但我们并不确定。
    for (EACH_HEADER) {
        // At launch time, we know preoptimized image refs are pointing at the
        // shared cache definition of a protocol.  We can skip the check on
        // launch, but have to visit @protocol refs for shared cache images
        // loaded later.
        if (launchTime && hi->isPreoptimized())
            continue;
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

    ts.log("IMAGE TIMES: fix up @protocol references");

    // 发现类别。只有在初始类别附件完成后才进行此操作。对于在启动时出现的类别,发现被延迟到_dyld_objc_notify_register调用完成后的第一个load_images调用。
    if (didInitialAttachCategories) {
        for (EACH_HEADER) {
            load_categories_nolock(hi);
        }
    }

    ts.log("IMAGE TIMES: discover categories");

    // 类别发现必须延迟,以避免其他线程在此线程完成修复之前调用新类别代码时可能出现的竞争。
    // +load handled by prepare_load_methods()
    // 实现非延迟类 (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t const *classlist = hi->nlclslist(&count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            addClassTableEntry(cls);

            if (cls->isSwiftStable()) {
                if (cls->swiftMetadataInitializer()) {
                    _objc_fatal("Swift class %s with a metadata initializer "
                                "is not allowed to be non-lazy",
                                cls->nameForLogging());
                }
                // fixme also disallow relocatable classes
                // We can't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

    ts.log("IMAGE TIMES: realize non-lazy classes");

    // 实现新解决的未来类,以防CF操纵它们
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls, nil);
            cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    ts.log("IMAGE TIMES: realize future classes");

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }


    // 打印过程中统计
    if (PrintPreopt) {
        static unsigned int PreoptTotalMethodLists;
        static unsigned int PreoptOptimizedMethodLists;
        static unsigned int PreoptTotalClasses;
        static unsigned int PreoptOptimizedClasses;

        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) {
                _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                             "in %s", hi->fname());
            }
            else if (hi->info()->optimizedByDyld()) {
                _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                             "in %s", hi->fname());
            }

            classref_t const *classlist = _getObjc2ClassList(hi, &count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                if (!cls) continue;

                PreoptTotalClasses++;
                if (hi->hasPreoptimizedClasses()) {
                    PreoptOptimizedClasses++;
                }
                
                const method_list_t *mlist;
                if ((mlist = cls->bits.safe_ro()->baseMethods)) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
                if ((mlist = cls->ISA()->bits.safe_ro()->baseMethods)) {
                    PreoptTotalMethodLists++;
                    if (mlist->isFixedUp()) {
                        PreoptOptimizedMethodLists++;
                    }
                }
            }
        }

        _objc_inform("PREOPTIMIZATION: %zu selector references not "
                     "pre-optimized", UnfixedSelectors);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                     PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                     PreoptTotalMethodLists
                     ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                     PreoptOptimizedClasses, PreoptTotalClasses, 
                     PreoptTotalClasses 
                     ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                     : 0.0);
        _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                     "pre-optimized", UnfixedProtocolReferences);
    }

#undef EACH_HEADER
}
  • 1.加载所有类到类的gdb_objc_realized_classes表中
  • 2.对所有类做重映射
  • 3.将所有SEL都注册到namedSelectors表中
  • 4.修复函数指针遗留
  • 5.将所有Protocol都添加到protocol_map表中
  • 6.对所有Protocol重映射
  • 7.初始化所有非懒加载类,进行rwro等操作
  • 8.遍历已标记的懒加载类,并做初始化操作
  • 9.处理所有Category,包括ClassMeta Class
  • 10.初始化所有未初始化的类
4.类的初始化

这个小结是在_read_images下的。
ps: 非懒加载类是重写了+load;懒加载类是没有重写+load

我们来看看_read_images内部对于类的加载初始化相关的操作。

发现类,循环地去readClass读取每一个类

readClass的工作是将已经分配内存空间的类和元类添加到allocatedClasses表(它在runtime_init()被初始化过)
特别注意:readClass内部并不会对我们自己写的类(如MyPerson类)进行rw创建和操作。此时类里还只有ro

有兴趣的可以使用这段代码在readClass插入这段代码去捕获到当前readClass的类是MyPerson类:(上帝视角)

    // 屏蔽掉除了MyPerson类对象的其它类
    const char *personName = "MyPerson";
    const char *mangledName = cls->nonlazyMangledName();
    auto my_ro = (const class_ro_t *)cls->data();
    auto my_isMeta = my_ro->flags & RO_META;
    if (strcmp(mangledName, personName) == 0 && !my_isMeta) { // 是MyPerson类对象,且不是元类
        printf("MyPerson类调用 realizeClassWithoutSwift\n");
    }

修复重映射的类(因ASLR产生的问题),这并不是关于类的初始化相关的。

注意:下面这张截图不是分类加载的入口,因为didInitialAttachCategories默认值是false,但是load_categories_nolock这个函数确实与分类加载有关,是在别的地方调用的,在第三个节点的时候介绍。

非懒加载类(实现了+load)初始化入口:

其中realizeClassWithoutSwift就是对类对象clsrw的创建和操作。
关于非懒加载类的初始化相关的代码就有了。

  • 1.初始化非懒加载类(重写+load的类)
    总结非懒加载类初始化流程:
    map_images -> map_images_nolock -> _read_images -> 发现类 readClass -> 加载类 realizeClassWithoutSwift -> 分类添加到类 methodizeClass

  • 2.初始化懒加载类(没重写+load的类)
    那么懒加载类(没有重写+load的类)的入口在哪儿呢?

分析懒加载类的初始化入口:
懒加载类是第一次被使用的时候加载,在加载的时候也会对rw进行创建和操作的,那样的话也会调用realizeClassWithoutSwift函数,在第一使用懒加载类的时候,就可以在realizeClassWithoutSwift函数内部拦截打印它调用的堆栈,从而分析出懒加载类的调用过程。

实践一下分析的结果:
1.声明一个MyPerson类,不重写它的+load,在main函数初始化,并且在realizeClassWithoutSwift函数内部对该类进行拦截,打印堆栈:

懒加载类的初始化是通过cache里面找缓存(汇编过程),没有找到调用_objc_msgSend_uncached -> lookUpImpOrForward -> ...
这里就不带着看消息发送源码了,有兴趣的朋友自行看看调用过程。

结论:懒加载类的加载过程:第一次接收到消息的时候才会被加载。

总结懒加载类初始化流程:
map_images -> map_images_nolock -> _read_images -> 类对象第一次接收到消息 -> cache里没有找到 _objc_msgSend_uncached -> lookUpImpOrForward -> realizeClassMaybeSwiftMaybeRelock -> 加载类 realizeClassWithoutSwift -> 分类添加到类 methodizeClass

ps:分类添加到类 methodizeClass 的内容放到第三个节点(分类的初始化)说。

三、分类的初始化

1.分类的加载的入口

无论是懒加载类还是非懒加载类,都会通过realizeClassWithoutSwift去加载类,这个函数内部会调用methodizeClass函数,它就是分类加载的入口

methodizeClass的声明:

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();
    
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro();
    auto rwe = rw->ext(); // 此时的rwe是null
    
    
    // 屏蔽掉除了MyPerson类对象的其它类 ps:这段代码不属于源码,用于下面查看ro和rwe的变化使用
    const char *personName = "MyPerson";
    const char *mangledName = cls->nonlazyMangledName();
    auto my_ro = (const class_ro_t *)cls->data();
    auto my_isMeta = my_ro->flags & RO_META;
    if (strcmp(mangledName, personName) == 0 && !my_isMeta) { // 是MyPerson类对象,且不是元类
        printf("MyPerson类调用 methodizeClass\n");
    }


    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods;
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
        if (rwe) {
            rwe->methods.attachLists(&list, 1);
        }
    }

    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        rwe->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        rwe->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    if (previously) { // previously传递进来的参数是nil,所以这段代码不会执行
        if (isMeta) {
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_METACLASS);
        } else {
            // When a class relocates, categories with class methods
            // may be registered on the class itself rather than on
            // the metaclass. Tell attachToClass to look for those.
            objc::unattachedCategories.attachToClass(cls, previously,
                                                     ATTACH_CLASS_AND_METACLASS);
        }
    }
    // 分类粘贴进类里
    objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods()) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name()));
        }
        ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
    }
#endif
}

此时的rwe为空呢,并不会调用if 判断rwe存在里的逻辑,暂时还不会对rwe进行attach操作。接着程序会走:objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

找到UnattachedCategoriesattachToClass函数声明:

    void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);
        
        // 屏蔽掉除了MyPerson类对象的其它类 - ps:这段代码不属于源码,用于下面查看ro和rwe的变化使用
        const char *personName = "MyPerson";
        const char *mangledName = cls->nonlazyMangledName();
        auto my_ro = (const class_ro_t *)cls->data();
        auto my_isMeta = my_ro->flags & RO_META;
        if (strcmp(mangledName, personName) == 0 && !my_isMeta) { // 是MyPerson类对象,且不是元类
            printf("MyPerson类调用 attachToClass\n");
        }

        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

此时的if (it != map.end())依旧不能通过。走到了这里依然没有对rwe的操作,就断掉了。
但是呢它在load_images函数内部又调用了loadAllCategories函数:

loadAllCategories的声明:

static void loadAllCategories() {
    mutex_locker_t lock(runtimeLock);

    for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        load_categories_nolock(hi);
    }
}

load_categories_nolock的声明:

static void load_categories_nolock(header_info *hi) {
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    size_t count;
    auto processCatlist = [&](category_t * const *catlist) {
        for (unsigned i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
            locstamped_category_t lc{cat, hi};

            if (!cls) {
                // Category的目标类缺失(可能是弱链接类)。
                // 忽略的Category。
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class",
                                 cat->name, cat);
                }
                continue;
            }

            // 屏蔽掉除了MyPerson类对象的其它类 - ps:这段代码不属于源码,它用于看ro和rwe的变化
            const char *personName = "MyPerson";
            const char *mangledName = cls->nonlazyMangledName();
            auto my_ro = (const class_ro_t *)cls->data();
            auto my_isMeta = my_ro->flags & RO_META;
            if (strcmp(mangledName, personName) == 0 && !my_isMeta) { // 是MyPerson类对象,且不是元类
                printf("MyPerson类调用 load_categories_nolock\n");
            }
            
            // 这个类别的过程。
            if (cls->isStubClass()) {
                // 存根类永远不会实现。存根类在被初始化之前并不知道它们的元类,因此我们必须将带有类方法或属性的类别添加到存根本身。
                // methodizeClass()将找到它们并将它们适当地添加到元类中。
                if (cat->instanceMethods ||
                    cat->protocols ||
                    cat->instanceProperties ||
                    cat->classMethods ||
                    cat->protocols ||
                    (hasClassProperties && cat->_classProperties))
                {
                    objc::unattachedCategories.addForClass(lc, cls);
                }
            } else {
                // 首先,将类别注册到它的目标类中。
                // 然后,如果类已经实现,则重新构建类的方法列表(等等)。
                if (cat->instanceMethods ||  cat->protocols
                    ||  cat->instanceProperties)
                {
                    if (cls->isRealized()) { // 如果类是已经初始化
                        attachCategories(cls, &lc, 1, ATTACH_EXISTING);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls);
                    }
                }

                if (cat->classMethods  ||  cat->protocols
                    ||  (hasClassProperties && cat->_classProperties))
                {
                    if (cls->ISA()->isRealized()) { // 如果类是已经初始化
                        attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
                    } else {
                        objc::unattachedCategories.addForClass(lc, cls->ISA());
                    }
                }
            }
        }
    };

    processCatlist(hi->catlist(&count));
    processCatlist(hi->catlist2(&count));
}

这里就对类进行了attachCategories的操作,里面就是对rwe的操作了

来看看attachCategories的声明:

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }
    
    // 屏蔽掉除了MyPerson类对象的其它类 - ps:这段代码不属于源码,它用于看ro和rwe的变化
    const char *personName = "MyPerson";
    const char *mangledName = cls->nonlazyMangledName();
    auto my_ro = (const class_ro_t *)cls->data();
    auto my_isMeta = my_ro->flags & RO_META;
    if (strcmp(mangledName, personName) == 0 && !my_isMeta) { // 是MyPerson类对象,且不是元类
        printf("MyPerson类调用 attachCategories\n");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    auto rwe = cls->data()->extAllocIfNeeded();

    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

去遍历分类的方法列表、属性列表、协议列表等等,通过attachLists进行rwe的操作:

attachLists的源码声明:

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
        // 三种情况
        if (hasArray()) {
            // 有很多个分类去声明 方法/关联属性/协议
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 本类中没有 方法/关联属性/协议
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 有一个分类去声明 方法/关联属性/协议
            // 1 list -> many lists
            Ptr oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }

attachLists会判断三种情况:多个分类去声明方法/关联属性/协议、本类中没有方法/属性/协议、只有一个分类去声明方法/关联属性/协议。

最终会把rwe里存储的方法列表/属性列表/协议列表都变成二维数组(只有两个元素,第一个元素是分类的,第二个元素是本类的)

所以在读取rwemethods/properties/protocols里面的内容看到的count个数是非常大的,上面说了,它存储的是一个二位数组。

举例methods

官方给的读取方法是不管它的数据结构是怎么样的只需要调用api就能得到正确的值:

结论:在调用本类与分类的同名方法时,会优先拿到分类对应的imp,而多个分类里有多个同名的方法,则是后编译的优先拿到它对应的imp

2.rwe在什么情况下产生

rwe存在于: objc_class -> bits -> rw -> rwe,在调用这个放的的时候才会去创建rwe:

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is())) {
            return v.get(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get(&ro_or_rw_ext));
        }
    }

于是全局搜索extAllocIfNeeded这个函数调用的地方,发现Runtime APIattachCategories函数内部才会调用。
但是,并非所有的分类加载都会进入attachCategories函数,它是由局限性的。
在下面会讲述的。
我这里先给结论:只有在Runtime API对类进行修改类和分类都为非懒加载的情况才会产生rwe(改变类的内部结构)

3.加载分类时,ro和rwe的变化因素

类和分类都能重写+load,重写了+load的类为非懒加载类,重写了+load的分类为非懒加载分类。

于是影响rorwe的因素就有这么个组合:

类(非) - 分类(非)
类(懒) - 分类(非)
类(非) - 分类(懒)
类(懒) - 分类(懒)

demo源码:

#import 
#import 

@interface MyPerson : NSObject {
    int _sum;
}

@property (nonatomic, copy) NSString *hobby;
- (void)speak;
- (void)printClassAllMethod:(Class)cls; // 打印当前类的MethodList
+ (void)method1;
@end

@implementation MyPerson

+ (void)load {
    NSLog(@"%s", __func__);
}

- (void)speak {
    NSLog(@"%s", __func__);
}

- (void)printClassAllMethod:(Class)cls {
    unsigned int count = 0;
    Method *methods = class_copyMethodList(cls, &count);
    for (unsigned int i = 0; i < count; i++) {
        Method method = methods[i];
        SEL sel = method_getName(method);
        IMP imp = class_getMethodImplementation(cls, sel);
        NSLog(@"%@ - %p", NSStringFromSelector(sel), imp);
    }
    free(methods);
}

+ (void)method1 {
    NSLog(@"%s", __func__);
}
@end


@interface MyPerson (Test)
@property (nonatomic, copy) NSString *name;
- (void)cate_instanceMethod;
+ (void)cate_classMethod;
@end

@implementation MyPerson (Test)

+ (void)load {
    NSLog(@"%s", __func__);
}

const char *MyNameKey = "MyNameKey";

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, MyNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, MyNameKey);
}

- (void)cate_instanceMethod {
    NSLog(@"%s", __func__);
}

+ (void)cate_classMethod {
    NSLog(@"%s", __func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"main");
        MyPerson *p = [MyPerson alloc];
        [p speak];
        p.name = @"安安";
        NSLog(@"%@", p.name);
    }
    return 0;
}
  • 先来看看ro

类(非) - 分类(非) :ro保存着本类里原有的方法,没有分类的

类和分类都实现了+load

其它三种组合:ro保存着本类和分类里全部的方法

类和分类其中一个没有实现+load或者都没有实现+load
  • 在来看看rwe:

上面分析出来了rwe是在attachCategories函数调用时产生的,只要看看这四个组合调用时,哪个调用过attachCategories就可以得出rwe的产生。

(在分析分类加载入口的时候,在源码里都加了拦截MyPerson加载的代码了)

类(非) - 分类(非) :

其它三种组合:

因为ro都保存了本类和分类的东西了,rwe就不需要存在了。

总结:
1.分类的加载在本类的加载之后,分类的东西会粘贴在本类之前。
2.在访问类与分类同名的方法属性协议等等,优先会访问分类的;多个分类存在同名的方法属性协议等等,先编译的后调用。
3.只有在非懒加载类和非懒加载分类的情况下,ro保存了本类的,rwe保存本类和分类的; 其它情况下ro保存了本类和分类的,没有rwe
4.Runtime API 对类进行更改也会产生rwe

四、load_images执行所有的+load

1.load_images的调用时机

dyld 941.5的源码:start -> prepare -> state.runAllInitializersForMain()

runAllInitializersForMain的dyld源码声明:

runAllInitializersForMain

load_images的调用时机是在libSystem的初始化后调用,去加载所有的+load

load_images函数声明:

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories();
    }

    // 如果这里没有+load方法,则不带锁返回。
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // 发现 load 方法
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);  // 准备load方法 - 加载load到内存
    }

    // 调用 +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

+load被加载到内存:prepare_load_methods
+load被调用:call_load_methods

2.类和分类的+load方法加载到内存

prepare_load_methods的声明:

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

    runtimeLock.assertLocked();

    // 类的加载
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    
    // 分类的加载
    category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

类的+load加载:schedule_class_load
分类的+load加载:add_category_to_loadable_list

  • 类的+load加载:schedule_class_load(remapClass(classlist[i]));
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls)
{
    if (!cls) return;
    ASSERT(cls->isRealized());  // _read_images should realize

    if (cls->data()->flags & RW_LOADED) return;

    // 确保superclass的load已经加载过了
    schedule_class_load(cls->getSuperclass());
    // 加载到内存
    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

在子类的+load被加载之前,先确保其父类的+load被加载完成。

add_class_to_loadable_list的声明:

/***********************************************************************
* add_class_to_loadable_list
* Class cls has just become connected. Schedule it for +load if
* it implements a +load method.
**********************************************************************/
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

类的+load加载到内存被保存到loadable_classes结构体数组,loadable_class这个结构体保存着类对象clsload method

  • 分类的+load加载:add_category_to_loadable_list(cat);
/***********************************************************************
* add_category_to_loadable_list
* Category cat's parent class exists and the category has been attached
* to its class. Schedule this category for +load after its parent class
* becomes connected and has its own +load method called.
**********************************************************************/
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    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++;
}

分类的+load加载到内存被保存到loadable_categories结构体数组,loadable_category这个结构体保存着分类对象catload method

3.类和分类的+load方法的调用

call_load_methods的声明:

void call_load_methods(void)
{
    static bool loading = NO;
    bool more_categories;

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;
    
    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. 重复调用class +load,直到没有加载为止
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2.所有分类+load调用一次
        more_categories = call_category_loads();

        // 3. 运行更多+reloads,如果有类或更多未尝试的类别
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);

    loading = NO;
}

首先把所有类里的+load方法调用一遍,再依次调用所有分类里的+load。
来看看是怎么调用的。

  • 调用类的+load:call_class_loads()
/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
static void call_class_loads(void)
{
    int i;
    
    // Detach current loadable list.
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    // Call all +loads for the detached list.
    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; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, @selector(load)); // 直接调用的方式
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

loadable_classes逐个取出类对象clsload method直接调用。

  • 调用分类的+load:call_category_loads()
/***********************************************************************
* call_category_loads
* Call some pending category +load methods.
* The parent class of the +load-implementing categories has all of 
*   its categories attached, in case some are lazily waiting for +initalize.
* Don't call +load unless the parent class is connected.
* If new categories become loadable, +load is NOT called, and they 
*   are added to the end of the loadable list, and we return TRUE.
* Return FALSE if no new categories became loadable.
*
* Called only by call_load_methods().
**********************************************************************/
static bool call_category_loads(void)
{
    int i, shift;
    bool new_categories_added = NO;
    
    // Detach current loadable list.
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;

    // Call all +loads for the detached list.
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;

        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, @selector(load));
            cats[i].cat = nil;
        }
    }

    // Compact detached list (order-preserving)
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    // Copy any new +load candidates from the new list to the detached list.
    new_categories_added = (loadable_categories_used > 0);
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)
                realloc(cats, allocated *
                                  sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }

    // Destroy the new list.
    if (loadable_categories) free(loadable_categories);

    // Reattach the (now augmented) detached list. 
    // But if there's nothing left to load, destroy the list.
    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }

    if (PrintLoading) {
        if (loadable_categories_used != 0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n",
                         loadable_categories_used);
        }
    }

    return new_categories_added;
}

loadable_categories逐个取出cat(它用来获取cls的)load method直接调用。

总结:
1.+load的加载和调用是线程安全的;
2.重写+load不需要写[super load],系统已经帮我们确定好父类的load了;
3.类的+load加载被保存到结构体指针数组,保存了clsmethod
4.所有分类的+load加载被保存到结构体指针数组,保存了catmethod
5.+load的调用顺序:父类 -> 子类 -> 分类
6.如果同一个类的两个分类都实现了+load,分类里的全部+load都会被调用:先编译的后调用
7.+load的调用是直接调用,并非消息发送;
8.Runtimeload加载和调用环节,更多的是获取类对象cls

五、unmap_image

dyld移除image的时候才会调用。

/***********************************************************************
* unmap_image
* Process the given image which is about to be unmapped by dyld.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
void 
unmap_image(const char *path __unused, const struct mach_header *mh)
{
    recursive_mutex_locker_t lock(loadMethodLock);
    mutex_locker_t lock2(runtimeLock);
    unmap_image_nolock(mh);
}

unmap_image_nolock的声明:

/***********************************************************************
* unmap_image_nolock
* Process the given image which is about to be unmapped by dyld.
* mh is mach_header instead of headerType because that's what 
*   dyld_priv.h says even for 64-bit.
* 
* Locking: loadMethodLock(both) and runtimeLock(new) acquired by unmap_image.
**********************************************************************/
void 
unmap_image_nolock(const struct mach_header *mh)
{
    if (PrintImages) {
        _objc_inform("IMAGES: processing 1 newly-unmapped image...\n");
    }

    header_info *hi;
    
    // 查找运行时image的header_info结构体
    for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
        if (hi->mhdr() == (const headerType *)mh) {
            break;
        }
    }

    if (!hi) return;

    if (PrintImages) {
        _objc_inform("IMAGES: unloading image for %s%s%s\n", 
                     hi->fname(),
                     hi->mhdr()->filetype == MH_BUNDLE ? " (bundle)" : "",
                     hi->info()->isReplacement() ? " (replacement)" : "");
    }
    

    // dyld移除image
    _unload_image(hi);

    // 从Mach-O的 Header list 中删除header_info
    removeHeader(hi);
    free(hi);
}

_unload_image的声明:

/***********************************************************************
* _unload_image
* Only handles MH_BUNDLE for now.
* Locking: write-lock and loadMethodLock acquired by unmap_image
**********************************************************************/
void _unload_image(header_info *hi)
{
    size_t count, i;

    loadMethodLock.assertLocked();
    runtimeLock.assertLocked();

    // 卸载未附加的分类和分类等待的+load。

    // 忽略__objc_catlist2。我们不支持卸载Swift,我们永远不会。
    category_t * const *catlist = hi->catlist(&count);
    for (i = 0; i < count; i++) {
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class

        // fixme for MH_DYLIB cat's class may have been unloaded already

        // unattached list
        objc::unattachedCategories.eraseCategoryForClass(cat, cls);

        // +load queue
        remove_category_from_loadable_list(cat);
    }

    // Unload classes.

    // Gather classes from both __DATA,__objc_clslist 
    // and __DATA,__objc_nlclslist. arclite's hack puts a class in the latter
    // only, and we need to unload that class if we unload an arclite image.

    objc::DenseSet classes{};
    classref_t const *classlist;

    classlist = _getObjc2ClassList(hi, &count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        if (cls) classes.insert(cls);
    }

    classlist = hi->nlclslist(&count);
    for (i = 0; i < count; i++) {
        Class cls = remapClass(classlist[i]);
        if (cls) classes.insert(cls);
    }

    // 首先,将类彼此分离。 然后free 类
    // 这避免了循环在父类之前卸载子类的错误

    for (Class cls: classes) {
        remove_class_from_loadable_list(cls);
        detach_class(cls->ISA(), YES);
        detach_class(cls, NO);
    }
    for (Class cls: classes) {
        free_class(cls->ISA());
        free_class(cls);
    }

    // XXX FIXME -- Clean up protocols:
    //  Support unloading protocols at dylib/image unload time

    // fixme DebugUnload
}
  • 1.当dyld移除某个image的时候,会调用unmap_image函数;
  • 2.遍历找到image对应Mach-Oheader_info
  • 3.从unattachedCategoriesloadable_categories两个分类相关列表里remove
  • 4.分离free
  • 5.从Mach-O中删除header_info

你可能感兴趣的:(iOS mapImage与loadImage底层探索)