1、load方法在什么什么时候调用?
类load方法
在dyld应用启动时,load_images的时期,进行单个类的收集,通过add_class_to_loadable_list收集在一张类的load方法表(loadable_class),然后通过call_class_loads统一递归load方法
分类load方法
在dyld应用启动时,load_images的时期,进行单个分类的收集,通过add_category_to_loadable_list收集在一张分类的方法表(loadable_category),然后通过call_category_loads统一递归load方法
关键函数
load_images
读取镜像,只保留了跟load有关部分
void
load_images(const char *path __unused, const struct mach_header *mh)
{
...
// 查找load方法
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// 调用 +load 方法(没有 runtimeLock - 可重入)
call_load_methods();
}
prepare_load_methods
收集load方法,关键函数是schedule_class_load内的add_class_to_loadable_list和add_category_to_loadable_list,对应的分别是类和分类。
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//通过遍历的方式收集类的load方法
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
//通过遍历的方式收集分类的load方法
category_t * const *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, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
schedule_class_load
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->getSuperclass());
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
call_load_methods
调用类与分类的load方法
void call_load_methods(void)
{
...
do {
// 1. 类load调用
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. 分类load调用
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
...
}
流程图
2、runtime是什么?
runtime是有C和C++、汇编实现的一套API。为OC语言加入了面向对象、运行时的功能 运行时(Runtime)是指将数据类型的确定由编译时推迟到了运行时(例如extension、Category,在底层数据结构对应的是ext,完全运行时加载的)
3、方法的本质,sel、imp是什么?两者间的关系是什么?
方法的本质是发送消息(objc_msg_send)
- 快速查找,通过objc_msg_send查找缓存消息(汇编方式,查找cache_t)
- 慢速查找:通过递归自己和父类,对应的函数为lookUpImpOrForward
- 查找不到消息:要进行动态方法解析,对应函数为resolveInstanceMethod
- 消息的快速转发:对应函数forwardingTargetForSelect
- 消息的慢速转发:对应函数methodSignatureForSelector和forwardInvocation
sel是方法编号,在read_images期间就编译进了内存,imp是函数实现指针,查找imp就是查找函数的过程
4、能否向编译后的得到类中添加实例变量?能否向运行时创建的类添加实例变量?
- 不能向编译后的类中添加实例变量
- 只要类没有注册到内存中是可以添加的 原因:我们编译好的实例变量存储位置是ro,一旦编译完成,内存结构就确定了,无法修改,但是可以添加属性和方法 objc_registerClassPair这个函数调用结束之后就无法再添加ivar了
5、[self class]和[super class]的区别和原理?
示例:下面代码[self class]和[super class]分别打印什么?
FFGirls.h和FFGirls.m
#import "FFPerson.h"
@interface FFGirls : FFPerson
@end
#import "FFGirls.h"
@implementation FFGirls
- (instancetype)init {
self = [super init];
if (self) {
NSLog(@"%@ - %@",[self class], [super class]);
}
return self;
}
@end
FFPerson.h和FFPerson.m
#import <Foundation/Foundation.h>
@interface FFPerson : NSObject
@end
#import "FFPerson.h"
@implementation FFPerson
@end
main.mm
int main(int argc, const char * argv[]) {
@autoreleasepool {
FFGirls *girls = [[FFGirls alloc] init];
}
return 0;
}
控制台打印结果:
2021-10-28 14:31:38.085530+0800 KCObjcBuild[27933:9731327] FFGirls - FFGirls
Program ended with exit code: 0
结论分析:
- (Class)class {
return object_getClass(self);
}
在代码层面,类FFGirls的完整init方法应该为- (instancetype)init(id self, SEL _cmd),这里面包含两个隐层参数,self与_cmd,在上述的案例中,self表示为FFGirls,class方法的底层实现为object_getClass(self),参数self为隐藏参数,即FFGirls,所以不管是[self class]或者[super class],本质上调用的都是class函数,传递的隐藏参数都是self,所以打印结果都为FFGirls。
6、下面的代码会输出什么?为什么bblv可以调用likeGirls方法?
FFPerson.h和FFPerson.m
#import <Foundation/Foundation.h>
@interface FFPerson : NSObject
- (void)likeGirls;
@end
#import "FFPerson.h"
@implementation FFPerson
- (void)likeGirls {
NSLog(@"%@ - %s", self, __func__);
}
@end
ViewController.m
#import "ViewController.h"
#import "FFPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
FFPerson *person = [FFPerson alloc];
[person likeGirls];
Class cls = [FFPerson class];
void *bblv = &cls;
[(__bridge id)bblv likeGirls];
}
@end
控制台输出
2021-10-29 15:32:38.457553+0800 001-内存平移问题[56669:10329070] <FFPerson: 0x600001808230> - -[FFPerson likeGirls]
2021-10-29 15:32:38.457636+0800 001-内存平移问题[56669:10329070] <FFPerson: 0x7ffee6fe8060> - -[FFPerson likeGirls]
结论分析:
方法调用的本质就是发送消息,通过objc_msg_send函数,通过类的isa指针找到类的首地址,通过内存平移的方式找到methodList,关于类的内存结构可以浏览WWDC2020关于runtime的优化,所以[person likeGirls]可以打印。至于为什么bblv也可以调用likeGirls?首先void *bblv = &cls;此行代码指针bblv拿到了cls的地址,也就是FFPerson的类地址,调用也就理所应当了,至于FFPerson这个类根本就不清楚是person对象调用它了,还是bblv指针地址指向它了。