iOS 类的加载分析
前言
通过前面对应用启动加载流程的分析《iOS 应用程序加载》,对程序的加载有一定的了解,是通过
objc_init()
方法注册回调函数,然后加载镜像文件,那么当镜像文件加载后,是怎么读到内存中的呢?有是以什么样的方式存储的呢?接下来我们分析一下libObjc
的源码,了解一下。
1. objc_init分析
在libObjc
的源码中查看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);
}
1.1 environ_init() 环境变量
void environ_init(void)
{
if (issetugid()) {
// All environment variables are silently ignored when setuid or setgid
// This includes OBJC_HELP and OBJC_PRINT_OPTIONS themselves.
return;
}
bool PrintHelp = false;
bool PrintOptions = false;
bool maybeMallocDebugging = false;
// Scan environ[] directly instead of calling getenv() a lot.
// This optimizes the case where none are set.
for (char **p = *_NSGetEnviron(); *p != nil; p++) {
if (0 == strncmp(*p, "Malloc", 6) || 0 == strncmp(*p, "DYLD", 4) ||
0 == strncmp(*p, "NSZombiesEnabled", 16))
{
maybeMallocDebugging = true;
}
if (0 != strncmp(*p, "OBJC_", 5)) continue;
if (0 == strncmp(*p, "OBJC_HELP=", 10)) {
PrintHelp = true;
continue;
}
if (0 == strncmp(*p, "OBJC_PRINT_OPTIONS=", 19)) {
PrintOptions = true;
continue;
}
const char *value = strchr(*p, '=');
if (!*value) continue;
value++;
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
if ((size_t)(value - *p) == 1+opt->envlen &&
0 == strncmp(*p, opt->env, opt->envlen))
{
*opt->var = (0 == strcmp(value, "YES"));
break;
}
}
}
// Special case: enable some autorelease pool debugging
// when some malloc debugging is enabled
// and OBJC_DEBUG_POOL_ALLOCATION is not set to something other than NO.
if (maybeMallocDebugging) {
const char *insert = getenv("DYLD_INSERT_LIBRARIES");
const char *zombie = getenv("NSZombiesEnabled");
const char *pooldebug = getenv("OBJC_DEBUG_POOL_ALLOCATION");
if ((getenv("MallocStackLogging")
|| getenv("MallocStackLoggingNoCompact")
|| (zombie && (*zombie == 'Y' || *zombie == 'y'))
|| (insert && strstr(insert, "libgmalloc")))
&&
(!pooldebug || 0 == strcmp(pooldebug, "YES")))
{
DebugPoolAllocation = true;
}
}
/*
自己修改判断条件,打印环境变量。
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
const option_t *opt = &Settings[i];
_objc_inform("%s: %s", opt->env, opt->help);
if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
}
*/
// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
if (PrintHelp || PrintOptions) {
if (PrintHelp) {
_objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
_objc_inform("OBJC_HELP: describe available environment variables");
if (PrintOptions) {
_objc_inform("OBJC_HELP is set");
}
_objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
}
if (PrintOptions) {
_objc_inform("OBJC_PRINT_OPTIONS is set");
}
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);
}
}
}
可以通过修改判断条件,打印所有的环境变量,如上源码中注释部分。我们也可以在lldb
上用命令export OBJC_HELP=1
来打印环境变量。
环境变量:
objc[36792]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[36792]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[36792]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[36792]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[36792]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[36792]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[36792]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[36792]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[36792]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[36792]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[36792]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[36792]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[36792]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[36792]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[36792]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[36792]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[36792]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[36792]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[36792]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[36792]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[36792]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[36792]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[36792]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[36792]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[36792]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[36792]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[36792]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[36792]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[36792]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[36792]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[36792]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[36792]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[36792]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[36792]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[36792]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[36792]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[36792]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[36792]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
上面代码都是我们的环境变量,可以在xcode
中设置环境变量,Edit Scheme
->Arguments
->Environment Variables
- 设置
OBJC_DISABLE_NONPOINTER_ISA
为YES
:设置nonpointer_isa
,优化内存结构。 - 设置
OBJC_PRINT_LOAD_METHODS
为YES
:可以打印所有实现了load
方法的类,对优化程序启动有帮助,能更快的找到实现load
方法的类。 - 设置
OS_ACTIVITY_MODE
为disable
,可以进行屏蔽系统日志。
1.2 tls_init()
进行线程
key
的绑定
void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
_objc_pthread_key = TLS_DIRECT_KEY;
pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
1.3 static_init()
运行C++静态构造函数,在
dyld
加载静态构造函数之前,libc
调用_objc_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]();
}
}
1.4 lock_init()
void lock_init(void)
{
}
空空如也,本身是通过C++写的,
objc
通用C++
和C
的那套锁的机制,只是在上层封装了一下。
1.5 exception_init()
异常处理函数,源码如下:
初始化libobjc的异常处理系统
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
// 初始化libobjc的异常处理系统
void exception_init(void)
{
old_terminate = std::set_terminate(&_objc_terminate);
}
在此方法中进行异常处理,先检测是否是
objc
异常,是,回调我们注册的回调对象callback
(即:uncaught_handler
),
/*
_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)();
}
}
}
1.5 _dyld_objc_notify_register()
源码如下:
//
// 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);
看注释,可以了解到:这个方法仅供
objc
运行时使用,
当objc
镜像被映射、未映射和初始化时调用的寄存器处理程序。Dyld
将使用包含objc-image-info
部分的图像数组调用mapped
函数。在调用Dyld
时,Dyld
将调用init
函数.
_dyld_objc_notify_register(&map_images, load_images, unmap_image)
接下来我们来探索一下
_dyld_objc_notify_register
的三个参数
-
map_images
-
load_images
-
unmap_image
2. map_images
查看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
映射到的给定镜像文件。 - 然后进入
map_images_nolock
函数
对
map_images_nolock
函数进行分析,去除打印和对hCount
的一些操作,直接定位到下面代码:
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
进入_read_images
镜像文件读取。
2.1 _read_images
由于_read_images
源码过于长,我们对关键代码进行分析:
#define EACH_HEADER \
hIndex = 0; \
hIndex < hCount && (hi = hList[hIndex]); \
hIndex++
if (!doneOnce) {
doneOnce = YES;
#if SUPPORT_NONPOINTER_ISA
// Disable non-pointer isa under some conditions.
# if SUPPORT_INDEXED_ISA
// Disable nonpointer isa if any image contains old Swift code
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
// Disable non-pointer isa if the app is too old
// (linked before OS X 10.11)
if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
DisableNonpointerIsa = true;
if (PrintRawIsa) {
_objc_inform("RAW ISA: disabling non-pointer isa because "
"the app is too old (SDK version " SDK_FORMAT ")",
FORMAT_SDK(dyld_get_program_sdk_version()));
}
}
// 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
if (DisableTaggedPointers) {
disableTaggedPointers();
}
initializeTaggedPointerObfuscator();
if (PrintConnecting) {
_objc_inform("CLASS: found %d classes during launch", totalClasses);
}
// 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);
ts.log("IMAGE TIMES: first time tasks");
}
- 看上面源码,当
doneOnce
为NO
时,即第一次进来时,会进入都if
判断里面,然后将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);
查看源码注释:
1. gdb_objc_realized_classes 中存放是所有的类,不管是否实现,都会存储在其中。
2. allocatedClasses 中存放是所有被开辟分配的类,
3. gdb_objc_realized_classes 表包含 allocatedClasses 表
由此:在_read_images
中,先加载所有类到gdb_objc_realized_classes
表中,那么为什么会创建两张表呢?是为了精确查找,不用每次都带着一个大表去查找。当在allocatedClasses
中没有查找到时,说明这个类没有初始化,就没有必要在去gdb_objc_realized_classes
中查找了。
当创建好两张表后,是怎样将所有的类添加到表中的呢?我们接着往下看
- 对所有类进行重映射
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
for (EACH_HEADER) {
// 从编译后的类列表中取出所有类,获取到的是一个classref_t类型的指针
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
for (i = 0; i < count; i++) {
// 数组中会取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系统类,例如CF、Fundation、libdispatch中的类。以及自己创建的类
Class cls = (Class)classlist[i];
// 通过readClass函数获取处理后的新类,
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;
}
}
}
上面这一段就是从编译后的类列表中遍历所有的类,并添加到对应的gdb_objc_realized_classes
和allocatedClasses
表中。那么,这个是在什么地方添加的呢?我们先留个疑问,先探索_read_images
整个流程。
- 将所有SEL都注册到
namedSelectors
哈希表中,
// 将所有SEL都注册到哈希表中,是另外一张哈希表
// Fix up @selector references
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->isPreoptimized()) 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的操作
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
}
- 将所有协议添加到
protocol_map
协议表中,并对所有的Protocol
重映射
// Discover protocols. Fix up protocol refs.
// 遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
// cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
// 获取protocol哈希表
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();
// 从编译器中读取并初始化Protocol
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
ts.log("IMAGE TIMES: discover protocols");
- 初始化所有非懒加载的类,并进行
ro
、rw
操作
// Realize non-lazy classes (for +load methods and static instances)
// 实现非懒加载的类,对于load方法和静态实例变量
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
// printf("non-lazy Class:%s\n",cls->mangledName());
if (!cls) continue;
// hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif
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
}
// 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
realizeClassWithoutSwift(cls);
}
}
在这里,是怎样给
rw
、ro
赋值的呢?我们先留下疑问,接着先探索_read_images
整个流程。
- 遍历
resolvedFutureClasses
数组,对已标记的懒加载类,初始化
// Realize newly-resolved future classes, in case CF manipulates them
// 遍历resolvedFutureClasses数组,实现懒加载的类
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);
cls->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
- 处理所有的
category
,包括Class
和MeteClass
。
// Discover categories.
// 发现和处理所有Category
for (EACH_HEADER) {
// 外部循环遍历找到当前类,查找类对应的Category数组
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
// 内部循环遍历当前类的所有Category
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
// 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 将Category添加到对应Class的value中,value是Class对应的所有category数组
addUnattachedCategoryForClass(cat, cls, hi);
// 将Category的method、protocol、property添加到Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
// 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
至此,
_read_images
的整个过程分析完毕。接下来,对这个过程中留下的疑问点,
比如:
- 编译后的类列表中遍历所有的类,并添加到对应的
gdb_objc_realized_classes
和allocatedClasses
表中的 - 在加载非懒加载类的时候,对
ro
、rw
是怎么处理赋值的?
接下来。我们对重点步骤进行分析。
2.2 readClass
在上述过程,我们了解到,在第一次进入创建gdb_objc_realized_classes
和allocatedClasses
两张表,来存储类。然后会对所有的类重映射。系统是通过readClass
函数对类进行处理的,查看readClass
的源码发现有下面源码:
看代码貌似是对ro
、rw
的处理,但经过断点调试,发现程序并未进到这里。
通过if (Class newCls = popFutureNamedClass(mangledName))
这个判断,得知:这里只是对未来待处理的类进行操作。
接着分析查看readClass
,发现下面代码:
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
查看addNamedClass
源码:
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
runtimeLock.assertLocked();
Class old;
if ((old = getClassExceptSomeSwift(name)) && old != replacing) {
inform_duplicate(name, old, cls);
// getMaybeUnrealizedNonMetaClass uses name lookups.
// Classes not found by name lookup must be in the
// secondary meta->nonmeta table.
addNonMetaClass(cls);
} else {
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
assert(!(cls->data()->flags & RO_META));
// wrong: constructed classes are already realized when they get here
// assert(!cls->isRealized());
}
在
addNamedClass
中,通过NXMapInsert(gdb_objc_realized_classes, name, cls)
将类添加到之前创建的gdb_objc_realized_classes
中。
addClassTableEntry
源码:
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
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.
assert(!NXHashMember(allocatedClasses, cls));
if (!isKnownClass(cls))
NXHashInsert(allocatedClasses, cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
在
addClassTableEntry
中,由于类已经初始化分配过空间,将其添加到之前创建的allocatedClasses
中。
- 由此可知:是通过
readClass
方法中的allocatedClasses
和addNamedClass
两个方法,将类添加到创建好的表中的。
在将类通过
readClass
进行重映射处理之后,会对比这两个类,如下图代码:
一般情况是相等的,当有懒加载的类时,会在readClass
中进行一些处理,致使newCls
和cls
不相等,然后将其加入到数组中。然后对懒加载的类进行初始化,上面已经讲过。
2.3 realizeClassWithoutSwift
在初始化所有非懒加载的类时,是怎样给rw、ro赋值的呢?查看源码主要有以下步骤:
remapClass
将类进行重映射addClassTableEntry(cls)
,将类插入到allocatedClasses
表中,如果存在就不插入realizeClassWithoutSwift(cls)
,实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
那么我们主要看一下
realizeClassWithoutSwift
源码:
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
// 在类cls上执行首次初始化,包括分配读写数据,不执行任何Swift端初始化。
static Class realizeClassWithoutSwift(Class cls)
{
runtimeLock.assertLocked();
// 初始化 ro rw 父类 元类
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
// 对类进行判断,下面有递归,isa的经典走位图,最终父类和元类指向nil
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
// 读取class中的data,对ro、rw赋值
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 {// 当前类,读取cls中的data数据,对rw->ro进行赋值(ro在编译时已经赋值),但是rw并没有赋值
// 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);
}
isMeta = ro->flags & RO_META;
rw->version = isMeta ? 7 : 0; // old runtime went up to 6
...
// 递归,父类和元类实现
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
...
// Update superclass and metaclass in case of remapping
// 父类和元类的归属关系,将父类和元类赋值到类的父类和isa,
cls->superclass = supercls; // 对Class 的superclass 赋值,
cls->initClassIsa(metacls); // 对Class 的Isa初始化,
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
...
// Connect this class to its superclass's subclass lists
// 双向链表指向关系 父类中可以找到子类 子类中也可以找到父类
// 将此类链接到其超类的子类列表
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls);
return cls;
}
进入
methodizeClass
static void methodizeClass(Class cls)
{
runtimeLock.assertLocked();
// 对类中的rw赋值
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// 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(); //读取ro里面的方法列表
if (list) {// 有,把ro里面的baseMethods赋值给rw中的methods
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties; //读取ro里面的属性列表
if (proplist) {// 有,把ro里面的baseProperties赋值给rw中的properties
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;//读取ro里面的协议列表
if (protolist) {// 有,把ro里面的baseProtocols赋值给rw中的protocols
rw->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, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
if (PrintConnecting) {
if (cats) {
for (uint32_t i = 0; i < cats->count; i++) {
_objc_inform("CLASS: attached category %c%s(%s)",
isMeta ? '+' : '-',
cls->nameForLogging(), cats->list[i].cat->name);
}
}
}
if (cats) free(cats);
}
由此看出:
rw
的赋值是在对非懒加载类初始化时赋值的,通过调用realizeClassWithoutSwift
,对类的rw->ro
赋值,在methodizeClass
中,对rw
的其他属性赋值。
而
ro
是在编译时就进行赋值的,只能读取,不能进行改变,rw
可以在进行调试的时候动态添加和处理方法、属性和协议。
methodizeClass
中,方法、属性和协议赋值给rw
的时候都是通过attachLists
进行添加,接下来,我们查看一下attachLists
源码
2.4 attachLists 分析
attachLists
源码:
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]));
}
}
有三种情况
- 0 对一,即:0 lists -> 1 list,直接通过
addedLists
直接赋值 - 1 对多,即:1 list -> many lists,将类开辟新的内存空间,将新数据进行存储
- 多对多,即:many lists -> many lists,扩大现有容器的内存,将旧数据放在指定的位置,将需要增加的数据放在前面
除了加载类的时候调用
addedLists
,还会在以下情况调用:
- 类的加载,处理方法、属性、协议时
methodizeClass
- 添加方法
addMethods
- 添加属性
_class_addProperty
- 添加协议
class_addProtocol
- 分类的加载
attachCategories
3. 总结
-
在
dyld
进入程序,读取数据后,通过map_Images
回调,在_read_images
中,将数据加载到内存中。1. 加载所有的类到 gdb_objc_realized_classes 和 allocatedClasses 两张表中 2. 通过 readClass 对所有类进行重映射 3. 将所有 SEL 注册到 namedSelectors 哈希表中 4. 将所有协议添加到 protocol_map 协议表中,并对所有的 Protocol 重映射 5. 初始化所有非懒加载的类,并进行ro、rw操作 6. 遍历resolvedFutureClasses数组,对已标记的懒加载类,初始化 7. 处理所有的category,包括Class和MeteClass。
-
readClass
1. 判断是不是后期加载的类,是,则读取class 的 date() 设置 rw 、 ro
2. addNamedClass(cls, mangledName, replacing)
3. addClassTableEntry(cls) 插入到表中 -
realizeClassWithoutSwift
实现非懒加载类的一些信息相关的操作,给类创建rw
结构,对父类,元类也进行相关初始化,对父类,元类,根类,子类进行相关的绑定 -
methodizeClass
把读取的ro
,写入到rw
。