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();
lock_init();
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
首先是调用的是environ_init();
这个方法。
这个方法里面主要是对环境变量的配置,方法中有段代码可以打印出所有的环境变量:
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
解锁这段打印代码的限制条件,就会打印下面所有的环境变量:
objc[1473]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[1473]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[1473]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[1473]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[1473]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[1473]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[1473]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[1473]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[1473]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[1473]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[1473]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[1473]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[1473]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[1473]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[1473]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[1473]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[1473]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[1473]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[1473]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[1473]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[1473]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[1473]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[1473]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[1473]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[1473]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[1473]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[1473]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[1473]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[1473]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[1473]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[1473]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[1473]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[1473]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[1473]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[1473]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[1473]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[1473]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[1473]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
以上环境变量中有我们常用的:
- OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
该环境变量会禁止non-pointer isa。这样所有的对象都会使用正常的,非优化的isa。该isa指向对象所属的类。
上图是我们正常的环境,可以看到通过x/4gx
打印出对象的地址,地址的第一段为对象的isa,和上面的p/x
打印的类对象的地址对比可以发现,两个地址是不相等了。
接下来我们设置环境变量OBJC_DISABLE_NONPOINTER_ISA
:
然后重新运行程序:
可以看出对象的isa和类对象的地址是相同的,也就是对象的isa真正指向了所属的类。然后我们通过
po
打印isa得到了所属的类。
现在我们大概明白了环境变量的意义。
- OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
开启该环境变量后会打印出所有实现+(void)load
方法的对象。我们可以通过这个来优化程序的启动速度。
接下来我们返回到_objc_init
函数,继续探究:
tls_init();
用于线程的key绑定,我们暂时略过;
然后就是static_init();
/***********************************************************************
* 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]();
}
}
通过注释我们也可以了解到,该函数的作用是:静态函数初始化。那么为什么不是在dyld中初始化呢?因为libc调用_objc_init()是在dyld之前,所以我们这里需要手动调用初始化。
注意:这里的静态函数是指的系统的静态函数,我们自定义的不算在里面。
然后调用的就是lock_init();
,我们跟踪该函数发现函数实现为空:
void lock_init(void)
{
}
为什么为空实现呢?可能是这部分代码没有开源。我们继续往下走,_objc_init(void)中接下来调用的是exception_init();
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
通过注释说明,这里是注册异常的回调
/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler.
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
if (PrintExceptions) {
_objc_inform("EXCEPTIONS: terminating");
}
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
// It's an objc object. Call Foundation's handler, if any.
(*uncaught_handler)((id)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
例如我们调用一个未实现的方法时,程序就会跑到这里来。
我们继续回到_objc_init(void)
方法中,接下来调用的就是_dyld_objc_notify_register(&map_images, load_images, unmap_image);
这个方法就是dyld中的方法
//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,
// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called
// initializers in that image. This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
此方法主要是注册了三个回调函数,回调函数会在dyld中进行调用。当images被maped、unmapped、initiallized时候的回调。
map_images回调
在map_images
中调用了map_images_nolock(count, paths, mhdrs);
然后map_images_nolock
中调用了_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
下面我们来解读_read_images
方法。
在该方法中有个以下的doneOnce判断
if (!doneOnce) {
doneOnce = YES;
.......
// 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);
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
......
}
在doneOnce(只执行一次)中创建了两张表:gdb_objc_realized_classes和allocatedClasses。然后我们搜索这两张表,可以看到它们的定义
// This is a misnomer: gdb_objc_realized_classes is actually a list of
// named classes not in the dyld shared cache, whether realized or not.
NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
gdb_objc_realized_classes
主要是用来存储那些不在共享缓存中,已经初始化或者未初始化的类
/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
static NXHashTable *allocatedClasses = nil;
allocatedClasses
主要是用来存储那些已经初始化的类。
我们继续往下读_read_images
方法的内容,主要包含以下几点:
- 类处理;
- 方法编号处理;
- 协议处理;
- 非懒加载类处理;
- 待处理的类;
- 分类处理;
上面我们是从宏观上列出了_read_images
的主要的功能,下面我们开始细致的分析。
类的处理
首先获取类的list列表:
// 从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
classref_t *classlist = _getObjc2ClassList(hi, &count);
然后for循环处理:
for (i = 0; i < count; i++) {
......
// 通过readClass函数获取处理后的新类,
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
......
}
在for循环中调用readClass
方法,对类进行处理。我们进入该方法看下,里面调用下面的两行代码
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
在这两个方法里面,分别将待处理的class存入到上面我们再doneOnce里面创建的两张表中。
然后我们返回过来继续看_read_images
下面的流程。我们暂时略过与方法无关的内容(协议、方法、分类等)。然后找到方法:
realizeClassWithoutSwift(cls);
进入realizeClassWithoutSwift(cls);
方法,看到对读取并且赋值了ro:
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw);
}
然后递归的对父类和元类进行初始化调用
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
然后绑定赋值绑定superClass以及isa
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
初始化创建superClass后,同样也会将superClass的subClass执行本class。
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
我们继续往下看,发现调用了下面的方法:
// Attach categories
methodizeClass(cls);
进入该方法看看
auto rw = cls->data();
auto ro = rw->ro;
读取到rw和ro
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
将ro中的methoList attach到rw中
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
将ro中的propertyList attach到rw中
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
将ro中的protocolList attach到rw中。
上面代码就是讲ro中的list 贴到rw中。为什么要这样做呢?我们搜索attachLists
方法,发现在
addMethod
class_addProtocol
_class_addProperty
attachCategories
中都调用了attachLists
方法,所以可以推测,我们平时用的分类方法、协议方法、动态的增加方法等都会添加到rw中。而ro中只是类中实现的方法。
tips: rw是readwrite的缩写,是可以动态修改的。ro是readonly的缩写,在编译时期确定的,不能动态修改。比如用户的ivarsList就存储在ro,所以我们不能动态的给对象添加成员变量。
下面我们来看下attachLists
方法是怎样对方法或者协议等列表进行attach处理的?
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;//10
uint32_t newCount = oldCount + addedCount;//4
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;// 10+4
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* 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;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
如果现在list中已经存在了一个Array。那么就会开辟一个新的list空间,将原来的list move到新创建的list的末尾,然后新插入的list copy到新创建list的头部。