iOS-底层探索12:dyld和objc的关联分析(类的加载上)

iOS 底层探索 文章汇总

目录

  • 一、前言
  • 二、_objc_init方法分析
  • 三、_dyld_objc_notify_register方法分析
  • 四、map_images 方法分析
  • 五、_read_images 方法分析
  • 六、readClass 方法分析


一、前言

上一篇文章iOS dyld流程分析中我们分析了dyld流程,知道了dyld会将库和代码编译加载到内存中。然后通过读取macho文件中的data获取到类信息,其中data中包含了ro、rw、rwe。后面的学习中我们将探索方法、属性、协议什么时候添加到类中的,rwe什么时候产生的?那么这篇文章我们就先分析dyldobjc的关联关系。

二、_objc_init方法分析

dyld加载库和代码后会进行objc的初始化,调用的方法为:_objc_init

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();
    runtime_init();
    exception_init();
    cache_init();
    _imp_implementationWithBlock_init();

    // 什么时候调用? images 镜像文件
    // map_images()
    // load_images()
    
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

environ_init()方法为运行时环境的初始化,打印出所有运行时环境变量

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第1张图片
environ_init

运行代码打印结果如下:

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第2张图片
image.png

由此可知设置环境变量OBJC_PRINT_LOAD_METHODS即可打印+load 方法

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第3张图片
image.png

所有调用了+load方法的类均会打印出来:

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第4张图片
image.png

_objc_init中调用的各个方法说明

  • environ_init():读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助。
  • tls_init():关于线程key的绑定-比如每条线程数据的析构函数。
  • static_init():运行C++静态构造函数。在dyld调用我们的静态构造函数之前,libc会调用_objc_init(),因此我们必须自己做。
  • runtime_init()runtime运行时环境初始化,里面主要是:unattachedCategoriesallocatedClasses后面会分析
  • exception_init():初始化libobjc的异常处理系统
  • cache_init():缓存条件初始化
  • _imp_implementationWithBlock_init():启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载trampolines dylib
  • _dyld_objc_notify_register(&map_images, load_images, unmap_image)dyld注册的地方。

其中的重点就是map_images方法和load_images方法的调用


三、_dyld_objc_notify_register方法分析

后续加载流程为:

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第5张图片
image.png

然而_dyld_objc_notify_register的调用在objc源码中,_dyld_objc_notify_register的实现却在dyld源码中,因此这里存在跨库调用。

_dyld_objc_notify_register实现如下:

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

load_images赋值给sNotifyObjCInit

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第6张图片
image.png

这里找到load_images方法的调用

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第7张图片
image.png

同理也找到map_images方法的调用

static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
...
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
...
}

map_images方法先调用,load_images方法后调用


四、map_images 方法分析

map_images方法的实现在objc源码中

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);
}
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_image

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
  if (!doneOnce) {...}

  // Fix up @selector references
  // 简单的字符串 -- 地址 字符串
  static size_t UnfixedSelectors;
  {...}

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

  // Discover classes. Fix up unresolved future classes. Mark bundle classes.
  bool hasDyldRoots = dyld_shared_cache_some_image_overridden();

  for (EACH_HEADER) {...}


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

    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    
    if (!noClassesRemapped()) {...}

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

#if SUPPORT_FIXUP
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {...}

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

    bool cacheSupportsProtocolRoots = sharedCacheSupportsProtocolRoots();

    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) {...}

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

    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) {...}

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

    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. rdar://problem/53119145
    if (didInitialAttachCategories) {...}

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

    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code before
    // this thread finishes its fixups.

    // +load handled by prepare_load_methods()

    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {...}

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

    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) {...}

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

    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }


    // Print preoptimization statistics
    if (PrintPreopt) {...}
}

_read_images方法功能如下:

  1. 条件控制进行一次的加载
  2. 修复预编译阶段的@selector混乱问题
  3. 错误混乱的类处理
  4. 修复重映射一些没有被镜像文件加载进来的类
  5. 修复一些消息!
  6. 当我们类里面有协议的时候:readProtocol
  7. 修复没有被加载的协议
  8. 分类处理
  9. 类的加载处理:实现非懒加载类(实现了+load方法的类和静态实例)
  10. 没有被处理的类 优化哪些被侵犯的类


五、_read_images 方法分析

_read_images方法中我们发现这样的代码:

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
        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;
            }
        }
...

readClass方法的作用:

iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第8张图片
iOS-底层探索12:dyld和objc的关联分析(类的加载上)_第9张图片

通过lldb断点调试我们发现从macho文件读取的cls经过readClass方法后cls绑定了的信息。


六、readClass 方法分析

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    
    if (missingWeakSuperclass(cls)) {... }
    
    cls->fixupBackwardDeployingStableSwift();

    Class replacing = nil;
    if (Class newCls = popFutureNamedClass(mangledName)) {...}
    
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(getClassExceptSomeSwift(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}

经过readClass方法中如下代码就将类的信息从macho中读取到了内存中,并加入类的实体表中。但此时内存中仅有类的地址和名字,还没有ro、rw、rew等数据。

 addNamedClass(cls, mangledName, replacing);
 addClassTableEntry(cls);

你可能感兴趣的:(iOS-底层探索12:dyld和objc的关联分析(类的加载上))