欢迎阅读iOS探索系列(按序阅读食用效果更加)c++
上篇文章iOS探索 类的加载过程分析了类的加载
过程,本文就来好好聊聊分类加载
的那些事面试
(请先对类的加载过程
有了必定了解以后再开启本文)express
为FXPerson
新建一个分类FXPerson-FX
数组
终端利用clang
输出cpp
安全
clang -rewrite-objc FXPerson+FX.m -o cate.cpp
复制代码
从cpp文件最下面看起,首先看到分类是存储在MachO文件的__DATA段的__objc_catlist
中bash
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_FXPerson_$_FX,
};
复制代码
其次能看到FXPerson分类的结构app
static struct _category_t _OBJC_$_CATEGORY_FXPerson_$_FX __attribute__ ((used, section ("__DATA,__objc_const"))) = {
"FXPerson",
0, // &OBJC_CLASS_$_FXPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_FXPerson_$_FX,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_FXPerson_$_FX,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_FXPerson_$_FX,
};
复制代码
来到objc源码中搜索category_t
查看底层中分类的结构(_category_t
搜索无果)ide
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
复制代码
根据FXPerson分类
结构和底层分类结构对比:函数
name
:类的名字,不是分类的名字cls
:类对象instanceMethods
:分类上存储的实例方法classMethods
:分类上存储的类方法protocols
:分类上所实现的协议instanceProperties
:分类所定义的实例属性,不过咱们通常在分类中添加属性都是经过关联对象来实现的_classProperties
:分类所定义的类属性为何分类的方法要将实例方法和类方法分开存呢?post
由于类和元类以前在不断编译,实例方法存在类中,类方法存在元类中,已经肯定好其方法归属的地方;而分类是后面才加进来的
经过上一篇文章咱们知道了类分为懒加载类
和非懒加载类
,他们的加载时机不同,那么分类又是如何呢?下面咱们就依次来进行探究
类、分类均不实现
+load
方法
已知懒加载类
很累,只有调用它发送消息时才会加载
而添加分类在两处出现了:_read_images
和methodizeClass
,咱们不妨来尝试一下
// 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);
// 首先,经过其所属的类注册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);
}
}
}
}
复制代码
static void methodizeClass(Class cls) {
...
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
...
}
复制代码
为了不其余类调用_read_images
和methodizeClass
(红色框)难以调试,分别往这两处添点代码(绿色框)从而更好的研究FXPerson
的类和分类
从左边的函数调用栈能够得出:
懒加载类
发送消息,lookupOrForward
->realizeClassMaybeSwiftAndLeaveLocked
realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
开始加载内存methodizeClass
处理父类、元类关系,调用了两次打印unattachedCategoriesForClass
返回NULL
_read_images
加载分类没有调用一不当心就翻车了,先换非懒加载类和懒加载分类
状况研究吧
只有类实现
+load
方法
①一样的研究方法,运行项目
dyld
->libSystem_initializer
->libdispatch_init
->_os_object_init
_objc_init
->map_images
->map_images_nolock
->_read_images
realizeClassWithoutSwift
->methodizeClass
加载类到内存中methodizeClass
处理父类、元类关系,调用了两次打印unattachedCategoriesForClass
返回NULL
_read_images
加载分类没有调用②又是和懒加载类和懒加载分类
同样的状况...继续探索一下rw
(看不懂就阅读类的结构分析)
(lldb) p/x cls
(Class) $0 = 0x0000000100001188
(lldb) p (class_data_bits_t *)0x00000001000011a8
(class_data_bits_t *) $1 = 0x00000001000011a8
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000103200060
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
version = 7
ro = 0x00000001000010e8
methods = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010b0
arrayAndFlag = 4294971568
}
}
}
properties = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
protocols = {
list_array_tt<unsigned long, protocol_list_t> = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff9383faa0
demangledName = 0x0000000000000000
}
(lldb) p $3.methods
(method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000010b0
arrayAndFlag = 4294971568
}
}
}
(lldb) p $4.list
(method_list_t *) $5 = 0x00000001000010b0
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "cate_doClass"
types = 0x0000000100000faa "v16@0:8"
imp = 0x0000000100000e00 (objc-debug`+[FXPerson(FX) cate_doClass] at FXPerson+FX.m:23)
}
}
}
(lldb) p $5->get(0)
(method_t) $7 = {
name = "cate_doClass"
types = 0x0000000100000faa "v16@0:8"
imp = 0x0000000100000e00 (objc-debug`+[FXPerson(FX) cate_doClass] at FXPerson+FX.m:23)
}
(lldb) p $5->get(1)
(method_t) $8 = {
name = "load"
types = 0x0000000100000faa "v16@0:8"
imp = 0x0000000100000e90 (objc-debug`+[FXPerson load] at FXPerson.m:12)
}
(lldb)
复制代码
第一次调用先处理元类关系metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
元类中存下了FXPerson类
的+load
方法和FXPerson分类
的+cate_doClass
方法
-cate_doInstance
方法
说明在methodizeClass
的unattachedCategoriesForClass
前已经把分类的方法加载到类中
③修改一下源码,将调试代码放到操做rw
的methods
以前,发现此时的methods
还没赋值
(lldb) p *$5
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory methodizeClass:类名 :FXPerson - 0x100001188 复制代码
断点来到methods
赋值以后,分类的方法已经躺在里面了
method_list_t *list = ro->baseMethods()
这一步只是对ro->baseMethods放到rw中
④从新跑项目,在第一个断点处打印ro
,分类方法已经存在了...
结论: 不论是懒加载类
或是非懒加载类
,懒加载分类
在编译时就肯定了
类、分类均实现
+load
方法
恢复成最初的调试代码,运行项目
①首先断点来到methodizeClass
两次unattachedCategoriesForClass
返回的都是NULL
②其次断点来到_read_images
的Discover categories
,按照图中的顺序依次调用
addUnattachedCategoryForClass
把类/元类和分类作一个关联映射remethodizeClass
调用attachCategories
处理分类static void remethodizeClass(Class cls) {
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
复制代码
remethodizeClass
调用attachCategories
处理分类
static void attachCategories(Class cls, category_list *cats, bool flush_caches) {
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
复制代码
attachCategories
分析:
+load
)while (i--)
中并非网上所说的按照Compile Sources
倒序加载的,先加载的是FXPerson+FX
,至于这里这么写多是为了方便吧
attachLists
添加分类的方法、属性、协议(类的加载过程有详细介绍)
memmove
将原数据移到末尾memcpy
把新数据拷贝到起始位置doInstance
,那么分类附加完成以后,类的方法列表里会有两个doInstance
运行时
在查找方法
时是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会返回imp
,却不知后面可能还有同样名字的方法只有分类实现
+load
方法
①首先断点来到_read_images
的Discover categories
,不走remethodizeClass
②断点来到methodizeClass
,此次终于经过unattachedCategoriesForClass
取到值了,而后经过attachCategories
添加
load_images
->
prepare_load_methods
->
realizeClassWithoutSwift
->...->
methodizeClass
(这个知识点后面会提到)
懒加载类 + 懒加载分类
第一次消息发送
的时候,而分类的加载则在编译时
懒加载类 + 非懒加载分类
_read_images
处,分类的加载则在编译时
非懒加载类 + 非懒加载分类
_read_images
处,分类的加载在类加载以后的reMethodizeClass
懒加载类 + 非懒加载分类
load_images
处,分类的加载在类加载以后的methodizeClass
+load
)+load
方法,就响应Compile Sources
最后一个分类+load
,响应非懒加载分类
——由于懒加载分类
在编译时就已经加载到内存,而非懒加载分类
运行时才加载+load
,响应Compile Sources
最后一个分类类拓展extension
又称做匿名的分类
,为了给当前类增长属性
和方法
具体由两种形式:
.m
文件中新增类拓展.h
文件数据很早的时候都会来到_read_image
,那正好在处理类时使用咱们的惯用伎俩
可是仔细一想不对呀,已经在类中有了方法实现了,此时的do_hExtension
不足以说明问题
那么能够经过查看属性的setter
和getter
方法来验证
经过上图就能够得出:
若是类拓展没有被引用(#import)就不会编译到到内存中
上篇文章讲到dyld初始化image
会触发load_image
,本文又提到了懒加载类
和非懒加载分类
状况下,分类加载到内存时的调用栈中有load_image
,那么咱们在该种状况下进行探索
在load_image
实现处打下断点,发现类和分类都没有打印+load
方法——load_image
先于+load
方法
prepare_load_methods
call_load_methods
发现并准备+load
方法
void prepare_load_methods(const headerType *mhdr) {
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
/*********************************************************************** * prepare_load_methods * Schedule +load for classes in this image, any un-+load-ed * superclasses in other images, and any categories in this image. **********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(Class cls) {
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
/*********************************************************************** * add_class_to_loadable_list * Class cls has just become connected. Schedule it for +load if * it implements a +load method. **********************************************************************/
void add_class_to_loadable_list(Class cls) {
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
复制代码
prepare_load_methods
分析:
_getObjc2NonlazyClassList
获取非懒加载类
列表schedule_class_load
遍历这些类
+load
方法,确保父类的+load
方法顺序排在子类的前面add_class_to_loadable_list
把类的+load
方法存在loadable_classes
里面
_getObjc2NonlazyCategoryList
取出非懒加载分类
列表realizeClassWithoutSwift
来防止类没有初始化(若已经初始化了则不影响)add_category_to_loadable_list
加载分类中的+load
方法到loadable_categories
此时就能看懂以前懒加载类
和非懒加载分类
的函数调用栈了
唤醒+load
方法
void call_load_methods(void) {
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
复制代码
objc_autoreleasePoolPush
压栈一个自动释放池do-while
循环开始
+load
方法直到找不到为止+load
方法关于initalize
苹果文档是这么描述的
Initializes the class before it receives its first message.
在这个类接收第一条消息以前调用。
Discussion
The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses.
Runtime在一个程序中每个类的一个程序中发送一个初始化一次,或是从它继承的任何类中,都是在程序中发送第一条消息。(所以,当该类不使用时,该方法可能永远不会被调用。)运行时发送一个线程安全的方式初始化消息。父类的调用必定在子类以前。
复制代码
而后咱们在objc源码
中lookUpImpOrForward
找到了它的踪影
lookUpImpOrForward
->initializeAndLeaveLocked
->initializeAndMaybeRelock
->initializeNonMetaClass
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {
...
if (initialize && !cls->isInitialized()) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
...
}
...
}
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock) {
return initializeAndMaybeRelock(cls, obj, lock, true);
}
static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked) {
···
initializeNonMetaClass(nonmeta);
···
}
复制代码
在initializeNonMetaClass
递归调用父类initialize
,而后调用callInitialize
/*********************************************************************** * class_initialize. Send the '+initialize' message on demand to any * uninitialized class. Force initialization of superclasses first. **********************************************************************/
void initializeNonMetaClass(Class cls) {
...
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
...
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
...
}
复制代码
callInitialize
是一个普通的消息发送
void callInitialize(Class cls) {
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
复制代码
关于initalize
的结论:
initialize
在类或者其子类的第一个方法被调用前(发送消息前)调用initialize
但不使用的状况下,是不会调用initialize
initialize
方法会比子类先执行initialize
方法时,会调用父类initialize
方法;子类实现initialize
方法时,会覆盖父类initialize
方法initialize
方法,会覆盖类中的方法,只执行一个(会执行最后被加载到内存中的分类的方法)从类结构
、消息发送
、dyld
到类与分类的加载过程
,笔者已经将加载->使用的流程进行一小波探究以后,接下来将开始干货分享——底层面试题