应用程序在启动的过程中,经过dyld一系列的操作,最后进入Objc的源码,入口就是_objc_init函数,我们从这个函数开始分析。
_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();
//全局静态C++函数调用
static_init();
//初始化两张表
runtime_init();
//异常捕捉初始化
exception_init();
#if __OBJC2__
//缓存
cache_t::init();
#endif
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
environ_init()
这个函数里,第一方法就是环境变量的初始化,平时我们都添加过环境变量,就是在这个初始化的。我们使用环境变量
- 在源码工程中打印环境变量
打印加载的image和library
OBJC_PRINT_IMAGES: log image and library names as they are loaded
测量image加载步骤的持续时间
OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods 打印所有+load方法的调用
OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods 打印所有+initialize方法的调用
OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod: 打印由+resolveClassMethod:和+resolveInstanceMethod:创建的方法
OBJC_PRINT_CLASS_SETUP: log progress of class and category setup 打印类和分类的加载
OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup 打印协议的加载
OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
OBJC_PRINT_CACHE_SETUP: log processing of method caches
OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
OBJC_PRINT_EXCEPTIONS: log exception handling
OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
OBJC_PRINT_CUSTOM_CORE: log classes with custom core methods
OBJC_PRINT_CUSTOM_RR: log classes with custom retain/release methods
OBJC_PRINT_CUSTOM_AWZ: log classes with custom allocWithZone methods
OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
OBJC_DEBUG_POOL_DEPTH: log fault when at least a set number of autorelease pages has been allocated
OBJC_DISABLE_VTABLES: disable vtable dispatch
OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al. 不使用tagged pointer
OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields 不使用non_pointer isa
OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
OBJC_DISABLE_FAULTS: disable os faults
OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
- 使用Xcode打印出环境变量
runtime_init()
初始化两张表
void runtime_init(void)
{
objc::unattachedCategories.init(32);
objc::allocatedClasses.init(); //用来保存 已经加载的类的表
}
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
把map_images load_images注册到dyld,由dyld调用方法。
- map_images 管理文件中和动态库中所有的符号(class,protocol,selector,category)
- 执行load方法
_read_images
从map_images->map_images_nolock->_read_images进入到加载image的关键函数。
_read_image主体流程
当我们把源码中的判断都收起来,每一块上面有一段注释,下面有一个log,每一块代码的作用就很清晰了,整体流程就比较明朗。部分代码截图:
- 条件控制进行一次的加载
- 修复预编译阶段的 @selector 的混乱问题
- 错误混乱的类处理 - 处理类
- 修复重映射一些没有被镜像文件加载进来的 类
- 修复一些消息
- 当我们类里面有协议的时候 : readProtocol
- 修复没有被加载的协议
- 分类处理
- 类的加载处理
- 没有被处理的类 优化那些被侵犯的类
!doneOnce 第一次加载
initializeTaggedPointerObfuscator(); 初始化tagged pointer
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize = (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes = NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
这里又创建了一张表,这张表是包含所有的类,无论是加载的还是没加载的。3/4是苹果常用的负载因子,用来做扩容的。这里计算了所有类的大小,然后乘以4/3,设置成表的大小,这样这张表就可以在扩容的情况正好保存所有的类。
readClass
这个方法中,我们只研究自定义的类,系统的类可能会有特殊的加载方式,所以我们判断加载自己的类的时候,进入断点,然后一步一步往下走。下面是省略掉部分源码的代码
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->nonlazyMangledName();
const char *person = "SwwPerson";
if (strcmp(mangledName, person) == 0) {
printf("自己的类");
}
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
if (headerIsPreoptimized && !replacing) {
// class list built in shared cache
// fixme strict assert doesn't work because of duplicates
// ASSERT(cls == getClass(name));
ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
} else {
if (mangledName) { //some Swift generic classes can lazily generate their names
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
addClassTableEntry(cls);
}
return cls;
}
这个方法的主要作用是两个函数addNamedClass(cls, mangledName, replacing)和addClassTableEntry(cls)
-
addNamedClass(cls, mangledName, replacing)本来类只是一个地址,没有名字,这个方法把类名付给了类。 -
addClassTableEntry(cls)
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
把类添加到初始化类的表中,元类也添加到表中。
realizeClassWithoutSwift
在把类添加名字,并且存入已初始化类的表中之后。需要真正的初始化这个类。之前谈到过类的结构,主要包括isa,superclass,cache,bits。在realizeClassWithoutSwift中将会对这些一一赋值。下面是删减过的代码
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
//正常的类进入这里,ro复制给rw
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
//创建一个空的cache给cls
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
cls->chooseClassArrayIndex();
//父类和元类递归初始化
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// Update superclass and metaclass in case of remapping
//设置父类,isa走位
cls->setSuperclass(supercls);
//设置元类
cls->initClassIsa(metacls);
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
//处理类的方法列表,分类等
// Attach categories
methodizeClass(cls, previously);
return cls;
}
懒加载类和非懒加载类
我们平时写代码的时候也使用过懒加载的方式,这个样既能节约内存,也能提高效率。苹果在设计类的加载的过程中,也使用了懒加载和非懒加载,他们的区别就是+load。
- 非懒加载类:实现了
+load方法就是非懒加载类。在main函数之前加载。 - 懒加载类:在第一次给这个类发送消息的时候初始化的。
类加载的流程总结
程序运行之前,会链接很多的image,通过read_images这个方法,把images都传进来。在加载类的过程中,分别对每一个image以及包含的每一个class加载,是遍历加载的。
类 和 分类
我们已经知道+load方法会使类变成非懒加载类,那么分类中使用+load会有什么影响呢,我们分为四种情况分别探索。
- 主类使用
+load分类使用+load
会调用attachCategories,ro里面只有主类的数据。方法列表是个二维数组[[分类A方法],[分类B方法],[主类方法]]。
- 主类使用
+load分类不使用
类为非懒加载类。不调用attachCategories。主类里的6个方法和分类里的2个方法都从mach-O中加载到ro。分类方法在前,主类方法在后。
- 主类不使用 分类使用
+load
情况同2.
- 类和分类都不使用
+load
类为懒加载类,当第一次收到消息时加载。主类里的6个方法和分类里的2个方法都从mach-O中加载到ro。分类方法在前,主类方法在后。