前言
作为一名iOS
开发人员,我们总是要不停的和类打交道,我们可能已经习惯创建类、实例化类、为类添加属性、添加方法等等一系列的相关操作。那么类是怎么加载到内存中的呢?类里面的数据又是什么时候确定的呢?
当启动一个App
的时候,系统需要把代码加载到内存中,编译成可执行文件,在经历系统初始化了libSystem、libdispatch、os_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();
// 设置线程的key
tls_init();
// 运行系统级别的c++ 静态构造函数
static_init();
lock_init();
// 初始化异常监听的回调函数
exception_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
从以上源码可以看出,_objc_init
会先执行一些配置相关的初始化:
- 环境变量的初始化
- 设置线程的key
- 运行系统级别的c++ 静态构造函数
- 初始化异常监听的回调函数
之后就会进行image
的映射和加载。
映射image
在这个环节,会将类的相关数据进行处理。首先,我们需要获取image
中的相关信息。
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
recursive_mutex_locker_t lock(loadMethodLock);
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_images
这个方法:
// 只展示重要的代码
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
......
// 1. 实例化存储类的hash表
if (!doneOnce) {
doneOnce = YES;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
}
// 2. 类处理
for (EACH_HEADER) {
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
}
}
// 3. 方法处理
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
SEL *sels = _getObjc2SelectorRefs(hi, &count);
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
}
// 4. 协议的处理
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// 5. 非懒加载类的处理
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);
realizeClassWithoutSwift(cls);
}
}
// 6. future类处理
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*/);
}
// 7. 分类的处理
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
}
}
......
}
分析上述方法:
1. 如果是第一次,创建两张hash表,用来存储类:
static NXHashTable *allocatedClasses = nil;
allocatedClasses
:所有通过objc_allocateClassPair
被allocated
的类或者元类
NXMapTable *gdb_objc_realized_classes;
gdb_objc_realized_classes
:不在共享缓存中且已经命名的类,不管实现与否
2. 读取类;修复未解决的future
类;标记bundle
类。
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName();
if (missingWeakSuperclass(cls)) {
addRemappedClass(cls, nil);
cls->superclass = nil;
return nil;
}
.......
// 如果是后期要处理的类,设置rw
if (Class newCls = popFutureNamedClass(mangledName)) {
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro;
memcpy(newCls, cls, sizeof(objc_class));
rw->ro = (class_ro_t *)newCls->data();
newCls->setData(rw);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
.......
addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);
return cls;
}
3. 对没有映射的引用类和其父类做重映射。
if (!noClassesRemapped()) {
for (EACH_HEADER) {
// 重映射Class,注意是从_getObjc2ClassRefs函数中取出类的引用
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
// fixme why doesn't test future1 catch the absence of this?
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
4. 将所有的SEL
都添加到一张名为namedSelectors
的NXMapTable
表中。
SEL sel_registerNameNoLock(const char *name, bool copy) {
return __sel_registerName(name, 0, copy);
}
static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{
// 如果存在namedSelectors,就先在namedSelectors中进行寻找, 匹配的上的话,就直接返回
if (namedSelectors) {
result = (SEL)NXMapGet(namedSelectors, name);
}
if (result) return result;
// 不存在namedSelectors,先创建一张namedSelectors表
if (!namedSelectors) {
namedSelectors = NXCreateMapTable(NXStrValueMapPrototype,
(unsigned)SelrefCount);
}
// 在表namedSelectors中插入
if (!result) {
result = sel_alloc(name, copy);
NXMapInsert(namedSelectors, sel_getName(result), result);
}
return result;
}
5. 如果支持SUPPORT_FIXUP
,获取所有的objc_msgSend
引用,然后对其进行修复
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
for (i = 0; i < count; i++) {
// 内部将常用的alloc、objc_msgSend等函数指针进行注册,并fix为新的函数指针
fixupMessageRef(refs+i);
}
}
6. 将所有的protocols
都添加到protocol_map
这张NXMapTable
表中。
static void
readProtocol(protocol_t *newproto, Class protocol_class,
NXMapTable *protocol_map,
bool headerIsPreoptimized, bool headerIsBundle)
{
......
auto insertFn = headerIsBundle ? NXMapKeyCopyingInsert : NXMapInsert;
// 先从protocol_map表中获取protocol_t
protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName);
// 取到就不做操作
// 没有就插入到protocol_map表中
if (oldproto) {
}
else if (headerIsPreoptimized) {
// 共享缓存会初始化协议对象本身,但是为了允许缓存外替换,我们现在需要将其添加到协议表中。
insertFn(protocol_map, installedproto->mangledName,
installedproto);
}
else if (newproto->size >= sizeof(protocol_t)) {
// 从未预先优化的image获取的协议
newproto->initIsa(protocol_class); // fixme pinned
insertFn(protocol_map, newproto->mangledName, newproto);
}
else {
// 重新实例化
size_t size = max(sizeof(protocol_t), (size_t)newproto->size);
protocol_t *installedproto = (protocol_t *)calloc(size, 1);
memcpy(installedproto, newproto, newproto->size);
installedproto->size = (typeof(installedproto->size))size;
installedproto->initIsa(protocol_class); // fixme pinned
insertFn(protocol_map, installedproto->mangledName, installedproto);
}
}
7. 初始化所有非懒加载类
注意:并不会实例化任何swift
端的类
- 初始化类,将类实例化rw结构,但是不对rw赋值
- 对当前类的父类、元类进行相关的初始化操作
- 对父类、元类、根类、子类进行相关的绑定操作
- 处理类的method list、protocol list、property list;绑定categories
// 只展示重要代码
// 将类插入hash表中
static void addClassTableEntry(Class cls, bool addMeta = true) {
runtimeLock.assertLocked();
if (!isKnownClass(cls))
NXHashInsert(allocatedClasses, cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
// 初始化所有非懒加载类,实例化rw数据
static Class realizeClassWithoutSwift(Class cls)
{
......
// rw创建
ro = (const class_ro_t *)cls->data();
// 如果是future类 rw已经被实例化,直接更新
// 注意,此处不对rw赋值
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 {
// 普通类,先开辟内存,创建rw结构
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()));
// 在重映射的情况下更新父类和元类
cls->superclass = supercls;
cls->initClassIsa(metacls);
// 将当前类链接到其父类的子类
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// 处理类的method list、protocol list、and property list
// 绑定 categories
methodizeClass(cls);
return cls;
}
将ro
的值赋给rw
,比如method_list_t、property_list_t、protocol_list_t
,并且把分类的method_list_t、property_list_t、protocol_list_t
绑定到类上。
// 只展示重要代码
static void methodizeClass(Class cls)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto 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);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
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*/);
......
}
列表绑定:
- 多对多:先将原来的列表进行扩容,然后将旧数据移动到列表的尾部位置,再把新的数据
copy
到列表头部位置。 - 0对1:直接将新数据赋值给列表
- 1对多或者1对多:
- 对列表进行扩容,如果存在旧数据,将原来的数据移动到列表的尾部,再把新的数据
copy
到列表头部位置。 - 对列表进行扩容,如果不存在旧数据,直接把新的数据
copy
到列表头部位置。
- 对列表进行扩容,如果存在旧数据,将原来的数据移动到列表的尾部,再把新的数据
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
// 将lists的数据往扩容后的copy,从原来oldCount的位置开始
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 将addedLists
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]));
}
}
8. 遍历future
类,并对其进行初始化
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[i];
realizeClassWithoutSwift(cls);
cls->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
9. 处理所有的分类,对未绑定的分类进行绑定操作。将分类的的method、protocol、property
添加到类。
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);
// 首先,通过其所属的类注册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;
}
}
// 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
// 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
总结:
加载类的时候,首先会在创建两张表gdb_objc_realized_classes
和allocatedClasses
用来存储类;接着从image
里读取类,并将类的SEL
存到namedSelectors
的表里,将protocols
存到protocol_map
表;然后实现非懒加载类的一些信息相关的操作,如给类创建rw
结构、对父类、元类也进行相关初始化、对父类、元类、根类、子类进行相关的绑定、处理类的method list、protocol list、property list、将ro
的值赋给rw
;然后再绑定分类,对分类的数据进行相关的操作。这样整个类就算是加载完成了。
Tips:
环境变量配置
当我们在scheme
中进行配置之后,重新编译程序的时候,就会在此时被系统获知,从而生效。
memmove && memcpy
void *memmove(void *s1, const void *s2, size_t n);
void *memcpy(void *restrict s1, const void *restrict s2, size_t n);
这两个函数都是将s2指向位置的n字节数据拷贝到s1指向的位置,区别就在于关键字restrict, memcpy假定两块内存区域没有数据重叠,而memmove没有这个前提条件。
我们可以看一下这两个方法实现的源码来加深印象:
#include <stddef.h> /* size_t */
void *memcpy(void *dest, const void *src, size_t n)
{
char *dp = dest;
const char *sp = src;
while (n--)
*dp++ = *sp++;
return dest;
}
void *memmove(void *dest, const void *src, size_t n)
{
unsigned char *pd = dest;
const unsigned char *ps = src;
if (__np_anyptrlt(ps, pd))
for (pd += n, ps += n; n--;)
*--pd = *--ps;
else
while(n--)
*pd++ = *ps++;
return dest;
}
__np_anyptrlt
是一个宏,用于结合拷贝的长度检测dest
与src
的位置,如果dest
和src
指向同样的对象,且src
比dest
地址小,就需要从尾部开始拷贝
由此可见memcpy
的速度比memmove
快一点,如果使用者可以确定内存不会重叠,则可以选用memcpy
,否则memmove
更安全一些。
关于懒加载类的加载流程
- 非懒加载类:调用了
load
方法,在加载数据的时候就会提前加载。 - 懒加载类:在使用的第一次才会加载。当我们调用这个类的方法的时候,如果是第一次,在消息查找的过程中就会加载这个类。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
......
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)
{
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
realizeClassWithoutSwift(cls);
} else {
cls = realizeSwiftClass(cls);
}
return cls;
}
网友评论