天天看点

iOS底层原理篇(三)----类的编译、链接与加载

我们都知道,App启动时,代码中+(void)load方法早于main函数调用,main函数是我们程序的入口,那么我们程序在main函数之前,都做了些什么工作呢?今天我们一起去探讨一下main函数之前的流程!

首先我们的应用程序,会依赖很多的库-----被操作系统写入内存的可执行代码的二进制!

库又有静态库如.a文件和动态库如.framework

  • App启动之后,main函数加载之前的过程:
    iOS底层原理篇(三)----类的编译、链接与加载
  • dyld::_main的流程
    iOS底层原理篇(三)----类的编译、链接与加载
    • 上图中的两张图片
      iOS底层原理篇(三)----类的编译、链接与加载
      iOS底层原理篇(三)----类的编译、链接与加载
    看到dylb::_main流程后,我们能知道,程序如何从dyld调用到objc库_objc_init()函数运行的过程!

那么,我们我们接下来看_objc_init()之后的调用流程及类是如何加载到内存的!

  • _objc_init(): 引导程序初始化,注册我们的镜像通知与dyld.在库初始化之前被libSystem调用!
    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();		//关于线程key的绑定 例如析构函数设置静态密钥key
      	
      	//运行c++静态构造函数,libc在dyld调用静态构造函数之前调用_objc_init(),
      	//所以我们必须自己做.
      	static_init();
      	lock_init();//空实现
      	//初始化libobjc的异常处理系统,由map_images()调用
      	exception_init();
      	//注册镜像加载通知回调
      	/**
      	注:仅供objc运行时使用
      	映射、未映射和初始化objc映像时要调用的寄存器处理程序
      	Dyld将使用包含objc-image-info部分的镜像数组调用“mapped”函数
      	那些是dylib的镜像会自动替换ref计数,因此objc不再需要对它们调用dlopen()来防止它们被卸载
      	在调用_dyld_objc_notify_register()期间,dyld将调用已经加载了objc镜像的“映射”函数
      	在以后的dlopen()调用期间,dyld还将调用“mapped”函数
      	当Dyld在该镜像中被称为初始化器时,Dyld将调用“init”函数
      	这是objc调用图像中的任何+load方法的时候
      	*/
      	_dyld_objc_notify_register(&map_images, load_images, unmap_image);
      }
               
  • map_images分析
    iOS底层原理篇(三)----类的编译、链接与加载
    iOS底层原理篇(三)----类的编译、链接与加载
    iOS底层原理篇(三)----类的编译、链接与加载
    iOS底层原理篇(三)----类的编译、链接与加载
    iOS底层原理篇(三)----类的编译、链接与加载
    iOS底层原理篇(三)----类的编译、链接与加载
    我们上面思维导图分析了map_images的流程,大致了解了类的读取,初始化,映射等的操作,下面我们看看load_images对类的加载!

在讲load_images方法之前,我们需要了解一些知识点:

  • 懒加载类与非懒加载类
    • 在read_images()思维导图中的:对所有类进行重映射步骤中,在源码中有一段注释这么写的: Realize non-lazy classes (for +load methods and static instances).有道翻译:实现非惰性类(用于+load方法和静态实例).我们可以看出,在read_images()中,初始化的都是非懒加载的类,既然有非懒加载的类,那么就应该有懒加载的类,那么非懒加载与懒加载类是怎么区分的呢?懒加载的类又是什么时候初始化的呢?
    • 非懒加载类在编译期就进行编译链接实现了,而懒加载类是在我们用到的时候才去调用,初始化!这两种类的区分方法就是:看这个类的实现中是否实现了+load方法!!!
    • 如果我们在类中实现了+load方法,那么这个类在编译期就会进行初始化实现,没有实现load方法,那么这个类在read_images()中就不会进行初始化,映射等的操作!
    • 那么.懒加载的类的调用时机,我们在这里提一下,后期会讲到这个!
    • 我的博客里有两篇是讲runtime的博文,其中我们有一个方法印象深刻:消息查找或转发:lookupimporforward:
      //此处就是懒加载类的调用,没有实现的类就会走到这里(消息转发过程调用!!!)
        //实现这个懒加载的类,非懒加载的类(编译期实现的)是不会走下面的流程的.
        if (!cls->isRealized()) {
        	cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        }
        static Class realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock) 
        {
        	return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
        }
        static Class realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked) {
        	lock.assertLocked();
        	if (!cls->isSwiftStable_ButAllowLegacyForNow()) {//OC
        		// Non-Swift class. Realize it now with the lock still held.
        		//返回类的实际类结构.就是进行类的初始化操作!!!
        		realizeClassWithoutSwift(cls);
        		if (!leaveLocked) lock.unlock();
        	} else {//swift
        		// Swift class. We need to drop locks and call the Swift
        		// runtime to initialize it.
        		lock.unlock();
        		cls = realizeSwiftClass(cls);
        		assert(cls->isRealized());    // callback must have provoked realization
        		if (leaveLocked) lock.lock();
        	}
        	return cls;
        }
                 
    • 所以.下面的load_images()也是对已经加入到类列表的类进行load操作!(不包含懒加载的类!)
  • load_images分析:
    void load_images(const char *path __unused, const struct mach_header *mh) {
      	//hasLoadMethods判断是否实现了+load方法,没有直接返回
      	//也就是说,我们加载的是实现了load方法的类(系统的一些类我们不去管)
      	// Return without taking locks if there are no +load methods here.
      	if (!hasLoadMethods((const headerType *)mh)) return;
      	recursive_mutex_locker_t lock(loadMethodLock);
      	
      	// Discover load methods
      	//找到实现了+load方法的类并加入到一个表,方便操作,load方法调用完成,这个表会被释放.
      	{
      		mutex_locker_t lock2(runtimeLock);
      		prepare_load_methods((const headerType *)mh);
      	}
    
      	// Call +load methods (without runtimeLock - re-entrant)
      	//调用
      	call_load_methods();
      }
               
iOS底层原理篇(三)----类的编译、链接与加载

上面我们经过分析,知道load方法的调用时机以及是如何调用的!

经过上面的流程,非懒加载类及分类的加载完成,以及load方法的调用时机以及是如何调用的,_objc_init()方法至此调用完成!下一篇我们会谈谈类的拓展、类拓展与分类的区别、类与分类的关系,以及分类属性关联、initialize方法调用时机等内容!

谢谢观赏!

继续阅读