1、 类(Class)结构
在源码中查看类信息
⚠️:错误跟踪: 在我们开发的工程中,通过command 跟踪进去的class 会进入到 runtime.h中的struct objc_class, 这里的是错误的,不是我们要的信息
⚠️:正确跟踪:在源码中,通过跟踪进入的class才是对的,最终在 objc-runtime-new.h 中 ,正确的应该是 继承objc_object 的结构体,如下:
struct objc_class : objc_object {
// Class ISA; // 这个信息来源于 继承 objc_object
Class superclass; // 内存 8位
cache_t cache; // 内存 16位
class_data_bits_t bits;
class_rw_t *data() { // 非常重要的数据
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
assert(isFuture() || isRealized());
data()->setFlags(set);
}
void clearInfo(uint32_t clear) {
assert(isFuture() || isRealized());
data()->clearFlags(clear);
}
// set and clear must not overlap
void changeInfo(uint32_t set, uint32_t clear) {
assert(isFuture() || isRealized());
assert((set & clear) == 0);
data()->changeFlags(set, clear);
}
………
}
类对象继承 objc_object
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
在看看非常重要的数据信息 class_rw_t *data()中的信息
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro; //⚠️: 实例方法列表、属性列表、协议列表 在这里
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
........
};
问题1:为什么实例对象的方法要存在类对象中?
想象一下,如果每生成一个实例都会将所有的方法实现拷贝过去,那将会占用很大的内存,所以类生成实例的时候将实例的isa指向自己,调用函数时在isa指向的类中去执行该调用哪个方法的逻辑。
2、属性、变量、方法存储在哪?
1、结构方式探索 (控制台打印)
在上面 struct class_rw_t
结构里有一个备注
方法列表、属性列表、协议列表存储在 const class_ro_t *ro
中
我们可以通过打印来验证这些信息:
在官网 中下载
objc4-xxx
的源码,整成可编译运行的工程
或者去这里直接下载可运行的工程(也有方法介绍)
//新建一个JEPerson类
@interface JEPerson : NSObject
{
NSString *_jeName;
}
@property (nonatomic, copy) NSString *jeNick;
@end
// 在main中 初始化这个类
Class cls = NSClassFromString(@"JEPerson");
通过x
指令打印cls信息 一步一步找到最后的信息
找成员/属性位置
x/5gx cls
0x1000021b0: 0x0000000100002188 (isa) 0x0000000100332140 (superclass)
0x1000021c0: 0x000000010032c490 0x0000002400000000 (这16位 是 cache_t ,结构体所占内存大小计算方法 可以去看之前的文章)
0x1000021d0: 0x0000000101816390 (bits)
// 拿到bits的地址在p
p 0x1000021d0
(long) $4 = 4294975952
//这个$4不是我们想要的 ,通过强转 再p
p (class_data_bits_t *)0x1000021d0
(class_data_bits_t *) $5 = 0x00000001000021d0
//想要得到data()里面的信息,bits.data() 的方法
p $5->data()
(class_rw_t *) $6 = 0x0000000100720450
//查看$6里面有那些信息
p *$6
(class_rw_t) $7 = {
flags = 2148007936
version = 0
witness = 0
ro = 0x0000000100002080
methods = {
list_array_tt = {
= {
list = 0x00000001000020c8
arrayAndFlag = 4294975688
}
}
}
properties = {
list_array_tt = {
= {
list = 0x0000000100002160
arrayAndFlag = 4294975840
}
}
}
protocols = {
list_array_tt = {
= {
list = 0x0000000000000000
arrayAndFlag = 0
}
}
}
firstSubclass = nil
nextSiblingClass = NSUUID
demangledName = 0x0000000000000000
}
// 查看ro里面的信息
p $6->ro
(const class_ro_t *) $7 = 0x0000000100002080
//看看$7是个什么东西
p *$7
(const class_ro_t) $8 = {
flags = 388
instanceStart = 8
instanceSize = 24
reserved = 0
ivarLayout = 0x0000000100000f4f "\x02"
name = 0x0000000100000f46 "JEPerson"
baseMethodList = 0x00000001000020c8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100002118
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002160
_swiftMetadataInitializer_NEVER_USE = {}
}
// 查看对象的信息
p $8.ivars
(const ivar_list_t *const) $9 = 0x0000000100002118
//查看$9的信息
p *$9
(const ivar_list_t) $9 = {
entsize_list_tt = {
entsizeAndFlags = 32
count = 2 //成员变量 2个
first = {
offset = 0x0000000100002178
name = 0x0000000100000f51 "_jeName"
type = 0x0000000100000f81 "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
// 有$8之后 可以p出属性
p $7.baseProperties
(property_list_t *const) $12 = 0x0000000100002160
//查看$12信息
p *$12
(property_list_t) $13 = {
entsize_list_tt = {
entsizeAndFlags = 16
count = 1 // 属性数量
first = (name = "jeNick", attributes = "T@\"NSString\",C,N,V_jeNick")
}
}
这里就得到了成员变量的信息 count = 2 第一个成员变量是 first={ _jeName }
属性的信息 count = 1 第一个成员变量是 first={ jeNick }
找方法存储位置
(lldb) x/5gx cls
0x1000022f0: 0x00000001000022c8 0x0000000100002340
0x100002300: 0x000000010032c490 0x0000002c00000000
0x100002310: 0x0000000101304180
(lldb) p 0x100002310
(long) $1 = 4294976272
(lldb) p (class_data_bits_t *)$1
(class_data_bits_t *) $2 = 0x0000000100002310
(lldb) p *$2
(class_data_bits_t) $4 = (bits = 4314907008)
(lldb) p $2->data()
(class_rw_t *) $5 = 0x0000000101304180
(lldb) p $5->ro
(const class_ro_t *) $6 = 0x0000000101304280
(lldb) p $6->baseMethodList
(method_list_t *const) $7 = 0x00000001000020c8
(lldb) p *$7
(method_list_t) $8 = {
entsize_list_tt = {
entsizeAndFlags = 26
count = 4
first = {
name = "readBook"
types = 0x0000000100000f87 "v16@0:8"
imp = 0x0000000100000cd0 (KCObjcTest`-[JEStudent readBook])
}
}
}
// 找到其他的方法
p $8.get(1)
p $8.get(2)
p $8.get(3)
.......
2、代码打印
//打印成员变量/属性
void testObjc_copyIvar_copyProperies(Class pClass){
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Ivar const ivar = ivars[i];
//获取实例变量名
const char*cName = ivar_getName(ivar);
NSString *ivarName = [NSString stringWithUTF8String:cName];
NSLog(@"class_copyIvarList:%@",ivarName);
}
free(ivars);
unsigned int pCount = 0;
objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
for (unsigned int i=0; i < pCount; i++) {
objc_property_t const property = properties[i];
//获取属性名
NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
//获取属性值
NSLog(@"class_copyProperiesList:%@",propertyName);
}
free(properties);
}
// 打印方法列表
// 如果传入类 就是打印的实例方法/静态方法
// 如果传入元类 就打印的是类方法
void testObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[i];
//获取方法名
NSString *key = NSStringFromSelector(method_getName(method));
NSLog(@"Method, name: %@", key);
}
free(methods);
}
void testInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
void testClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
NSLog(@"%s",__func__);
}
void testIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
3、方法
在调试过程中,最后打印出来的方法 包含三个信息name
、types
、imp
,我们查看objc_method源码查看结构
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
分别代表:方法名、方法类型(方法编码)、方法实现
方法名很简单,这里我们来研究方法类型(方法编码)究竟是怎样的含义
定义一个类 Student
并实现几个方法(在m文件中实现,h中可以不定义)
- (NSString *)mehtodOne:(int)a
str:(NSString *)str {
return @"";
}
- (NSArray *)mehtodTwo:(NSArray *)a
str:(NSString *)str
count:(NSInteger)count {
return [NSArray new];
}
-(void)readBook {
}
+ (NSInteger)methodForClass:(NSInteger)a
time:(long)time {
return 1;
}
我们来打印这些方法的信息
#import
Student *obj = [Student new];
- (void)methodInfo:(id)obj
{
unsigned int methodCount = 0;
Method *methodList = class_copyMethodList([obj class], &methodCount);
for (NSInteger i = 0; i < methodCount; i ++) {
Method method = methodList[i];
SEL methodName = method_getName(method);
NSLog(@"方法名:%@", NSStringFromSelector(methodName));
// 获取方法的参数类型
unsigned int argumentsCount = method_getNumberOfArguments(method);
char argName[512] = {};
for (unsigned int j = 0; j < argumentsCount; ++j) {
method_getArgumentType(method, j, argName, 512);
NSLog(@"第%u个参数类型为:%s", j, argName);
}
char returnType[512] = {};
method_getReturnType(method, returnType, 512);
NSLog(@"返回值类型:%s", returnType);
// type encoding
NSLog(@"TypeEncoding: %s", method_getTypeEncoding(method));
}
}
打印结果为:
方法名:mehtodOne:str:
第0个参数类型为:@
第1个参数类型为::
第2个参数类型为:i
第3个参数类型为:@
返回值类型:@
TypeEncoding: @28@0:8i16@20
方法名:mehtodTwo:str:count:
第0个参数类型为:@
第1个参数类型为::
第2个参数类型为:@
第3个参数类型为:@
第4个参数类型为:q
返回值类型:@
TypeEncoding: @40@0:8@16@24q32
方法名:readBook
第0个参数类型为:@
第1个参数类型为::
返回值类型:v
TypeEncoding: v16@0:8
问题
问题1、方法的参数个数和打印的参数个数不一致?
问题2、类方法去哪了?
关于问题1:
这里打印的参数并不是我们定义的方法参数 而是底层objc_msgSend的参数
在底层调用objc_msgSend的时候,是有2个固定参数(id)self ---> 方法的调用者 和 SEL _cmd ---->调用的方法名,后面跟的是其他的参数信息
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
比如方法1: 在编译的时候会被转换为
NSString *returnValue = ((NSString * (*)
(id,
SEL,
NSString *,
NSNumber *))
objc_msgSend)
(obj,
@selector(mehtodOne:str:),
a
@"");
方法二会被编译为
NSArray *returnValue = ((NSArray * (*)
(id,
SEL,
NSArray *,
NSString *,
NSInteger))
objc_msgSend)
(obj,
@selector(mehtodTwo:str:count:),
@[]
@"",
a);
方法三会被编译为
((void (*)(id, SEL))objc_msgSend)(obj, @selector(readBook));
这样就解释了参数的数量。
- 方法编码的含义:
用方法一@28@0:8i16@20
举例
第一个@表示的是返回值的标识
NSString->@
void -> v
int -> i
28表示的是 所有参数的总长度
@ 表示第一个参数的 类型 ,这里就是 self的类型
0 表示从第0位开始
:表示第二位参数的类型,这里是 SEL
8 表示从第8位开始,因为前面的一个参数是self(占位8)
i 表示第三位参数的类型 这里是 int
16 表示从第16位开始 ,因为前面有2个参数self(占位8) SEL(占位8)
@ 表示第四个参数的 类型 ,这里就是 NSString *的类型
20 表示从第20位开始 ,因为前面有3个参数self(占位8) SEL(占位8)int (占位4)
最后一个参数类型为NSString( 占位8) 所以最前面总长度为28
具体什么类型占用多少字节 可参照这篇文章 的 【补充 sieof()知识】这部分知识点
想要知道每个类型的占位符 可以通过以下方法打印
(官方也有相关文档介绍)
// 调用
- (void)viewdidLoad{
logTypes()
}
void logTypes(){
NSLog(@"char --> %s",@encode(char));
NSLog(@"int --> %s",@encode(int));
NSLog(@"short --> %s",@encode(short));
NSLog(@"long --> %s",@encode(long));
NSLog(@"long long --> %s",@encode(long long));
NSLog(@"unsigned char --> %s",@encode(unsigned char));
NSLog(@"unsigned int --> %s",@encode(unsigned int));
NSLog(@"unsigned short --> %s",@encode(unsigned short));
NSLog(@"unsigned long --> %s",@encode(unsigned long long));
NSLog(@"float --> %s",@encode(float));
NSLog(@"bool --> %s",@encode(bool));
NSLog(@"void --> %s",@encode(void));
NSLog(@"char * --> %s",@encode(char *));
NSLog(@"id --> %s",@encode(id));
NSLog(@"Class --> %s",@encode(Class));
NSLog(@"SEL --> %s",@encode(SEL));
int array[] = {1,2,3};
NSLog(@"int[] --> %s",@encode(typeof(array)));
typedef struct person{
char *name;
int age;
}Person;
NSLog(@"struct --> %s",@encode(Person));
typedef union union_type{
char *name;
int a;
}Union;
NSLog(@"union --> %s",@encode(Union));
int a = 2;
int *b = {&a};
NSLog(@"int[] --> %s",@encode(typeof(b)));
}
//打印结果
char --> c int --> i
short --> s long --> q
long long --> q unsigned char --> C
unsigned int --> I unsigned short --> S
unsigned long --> Q float --> f
bool --> B void --> v
char * --> * id --> @
Class --> # SEL --> :
int[] --> [3i] struct --> {person=*i}
union --> (union_type=*i) int[] --> ^i
官方文档搜索路径 comm+shift+0 ->
问题2: 类方法在哪呢?
上面打印出来的方法信息只有实例方法,那么类方法去哪里了呢?
注意上面获取方法列表的使用
Method *methodList = class_copyMethodList([obj class], &methodCount);
这样一句代码,这里是获取obj的类对象里面的方法
那么如果需要获取类方法,这里获取方法信息的对象就应该是元类对象
Class class = [obj class];
Class metaClass = object_getClass(class);
Method *methodList = class_copyMethodList([obj class], &methodCount);
方法名:methodForClass:time:
第0个参数类型为:@
第1个参数类型为::
第2个参数类型为:q
第3个参数类型为:q
返回值类型:q
TypeEncoding: q32@0:8q16q24
验证方法存储位置
定义一个类 : JEObject
JEObject声明并实现2个方法
- (void)je_instanceMethod;
+ (void)je_classMethod;
这里介绍三个API
Method class_getInstanceMethod(Class cls, SEL sel)
Method class_getClassMethod(Class cls, SEL sel)
IMP class_getMethodImplementation(Class cls, SEL sel)
- class_getInstanceMethod(Class cls, SEL sel) :从cls(类对象/元类对象)获取实例方法sel
执行代码:
Method method1 = class_getInstanceMethod([JEObject class], @selector(je_instanceMethod));
Method method2 = class_getInstanceMethod(objc_getMetaClass("JEObject"), @selector(je_instanceMethod));
Method method3 = class_getInstanceMethod([JEObject class], @selector(je_classMethod));
Method method4 = class_getInstanceMethod(objc_getMetaClass("JEObject"), @selector(je_classMethod));
NSLog(@"method1 = %p \n\
method2 - %p\n\
method3 - %p\n\
method4 - %p",method1,method2,method3,method4);
打印结果:
method1 = 0x10f47e1b8
method2 - 0x0
method3 - 0x0
method4 - 0x10f47e150
从打印结果来看:
method1和method4 是有值的,2、3为nil,也就是说:从类对象中能拿到实例方法,从元类中可以拿到类方法,换句话就是:实例方法在对象中,而类方法 在元类对象中
- class_getClassMethod(Class cls, SEL sel) 从cls(类对象/元类对象)获取类方法sel
执行代码:
Method method1 = class_getClassMethod([JEObject class], @selector(je_instanceMethod));
Method method2 = class_getClassMethod(objc_getMetaClass("JEObject"), @selector(je_instanceMethod));
Method method3 = class_getClassMethod([JEObject class], @selector(je_classMethod));
Method method4 = class_getClassMethod(objc_getMetaClass("JEObject"), @selector(je_classMethod));
NSLog(@"method1 = %p \n\
method2 - %p\n\
method3 - %p\n\
method4 - %p",method1,method2,method3,method4);
打印结果:
method1 = 0x0
method2 - 0x0
method3 - 0x10e054178
method4 - 0x10e054178
⚠️:这里就很奇怪了,method4不仅有值,而且和method3是一样的,为什么呢?
不懂就看源码
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
首先先要明白:类方法是存储在元类的方法列表中,这里传入的cls 如果是[JEObject class] 只是一个类,而不是元类 那么就会自动去找到其元类,并在其元类中找相应的方法
,如果传入的是元类objc_getMetaClass("JEObject"),那么就直接在其自身的方法列表中去找
method3和method4虽然写法不一样,但是进入源码中一看,其实意思是一样的,所以最后的打印结果是一样的。
- class_getMethodImplementation(Class cls, SEL sel)
执行代码:
IMP imp1 = class_getMethodImplementation([JEObject class], @selector(je_instanceMethod));
IMP imp2 = class_getMethodImplementation([JEObject class], @selector(je_classMethod));
IMP imp3 = class_getMethodImplementation(objc_getMetaClass("JEObject"), @selector(je_instanceMethod));
IMP imp4 = class_getMethodImplementation(objc_getMetaClass("JEObject"), @selector(je_classMethod));
NSLog(@"imp1 = %p \n\
imp2 - %p\n\
imp3 - %p\n\
imp4 - %p",imp1,imp2,imp3,imp4);
打印结果:
imp1 = 0x106763a40
imp2 - 0x7fff503b2400
imp3 - 0x7fff503b2400
imp4 - 0x106763a70
⚠️:这里也很奇怪,imp2和imp3不仅有值,而且还是一样的,为什么呢?
不懂还是看源码
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
一看就明白了,如果没有最后返回的是消息转发,
在控制台上用po命令分别打印imp1、imp2、imp3、imp4得到的结果是:
(lldb) po imp1
(`-[JEObject je_instanceMethod] at JEObject.m:16)
(lldb) po imp2
(libobjc.A.dylib`_objc_msgForward)
(lldb) po imp3
(libobjc.A.dylib`_objc_msgForward)
(lldb) po imp4
(`+[JEObject je_classMethod] at JEObject.m:19)
方法的调用流程
实例方法调用过程
那么方法的调用的具体流程是什么样的呢?
iOS objc_msgSend消息发送机制
神经病院Objective-C Runtime住院第二天——消息发送与转发 - 详细的源码分析
比如执行代码:
Student *obj = [Student new];
[obj readBook];
- 编译过程中
首先会转换成objc_msgSend函数
Student *obj = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)obj, sel_registerName("readBook"));
转换命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 类名.m -o out.cpp
- 执行过程中、
1、判断obj是否为nil,如果为nil,什么都不会发生
2.在对象
的<缓存方法列表>【也就是类对象的缓存中】 中去找要调用的方法,找到直接调用
3.对象
的<缓存方法列表> 里没找到, 就去<类
的缓存列表>去找,如果没有你缓存,就去方法列表>里找,找到了就调用并缓存。
4.还没找到,说明这个类自己没有了,就会通过isa去向其父类
里执行2、3。
5.当父类指向null的时候还没找到【对象的父类->父类的父类-> ..... -> NSObjec -> nil】,那么就是没有了,就进行动态解析
6.如果没有进行动态解析,那么就会报错崩溃。
上面说了,方法找到了之后的操作是 将其缓存起来并调用,,如果直接在类对象中找到了方法,我们知道 是直接缓存在类对象的缓存信息中。那么如果是在superClass中找到方法。缓存是存在那个位置呢?在源码中能找到答案:
objc_msgSend -> _class_lookupMethodAndLoadCache3 -> lookUpImpOrForward 中 在类的方法列表中找到时有这样一段代码:
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
在父类中找到方法的代码:
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
注意log_and_fill_cache
这个方法和解释备注,
如果在父类中找到方法,就在这个类中进行缓存
也就是说 方法缓存的位置是在类对象的缓存信息中,而不是在父类中找到的就缓存在父类
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
/**
JEStudent:JEPerson
cls: JEStudent
imp: (KCObjcTest`-[JEPerson superClass])
sel: (SEL) sel = "superClass"
receiver: (JEStudent *) receiver = 0x0000000101437030
implementer: JEPerson
*/
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill(cls, sel, imp, receiver);
}
补充:
类的方法列表位置:
class_rw_t *data() —>
class_ro_t *ro —>
baseMethodList
类方法的调用过程
类方法的调用过程和实例方法步骤大致一致,只是找方法的地方不一样
1.在
类
的<缓存方法列表>【也就是元类的缓存中】 中去找要调用的方法,找到直接调用
2.类
的<缓存方法列表> 里没找到,就去<类的元类
方法列表>里找,找到了就调用并缓存。
3.还没找到,说明这个类自己没有了,就会通过isa去向其元类的父类
里执行1、2。
4.直到最后的父类
指向null的时候【元类->元类的父类(和父类的元类是一个东西)->根源类,根源类的父类->NSobjec->nil】还没找到,那么就是没有了,就进行动态解析
5.如果没有进行动态解析,那么就会报错崩溃。
来看下面这个打印
NSObject添加一个拓展类
@interface NSObject (JE)
+ (void)je_method;
- (void)je_method;
@end
实现实例方法(⚠️类方法并没有实现,而且实例方法和类方法的方法名是一致的)
- (void)je_method
{
NSLog(@"实例方法");
}
执行以下代码:
[NSObject je_methoded];
NSObject *obj = [NSObject new];
[obj je_method];
先考虑这样能否正确执行,如果能,打印结果是什么,为什么?
打印结果
实例方法
实例方法
为什么能正确打印,而且执行的都是实例方法? 以为执行类方法的时候,在其元类中没找到其方法就去去元类的父类中去找,而对于NSObject的元类的父类是其本身,所以最后有执行了其实例方法
2020.09.18 补充
在Class结构中 的data() 信息class_rw_t *data()
在它的结构中,rw结构中有 method等,rw的ro里面 也有method等。他们之间有什么区别?
ro里面 是只读属性,存的是编译是就产生的信息,比如类/类拓展 包含的方法、属性等。
而ro外层 rw中的 method等 是 类本身的信息 + 分类方法 + 通过runtime 动态添加的方法
类似于:
rw中的method = ro中的basemethod + 分类的方法 + class_addMethod 。
不管是编译的产生的方法 还是动态运行时 新增的方法 ,都会存在于 rw的methods中。