在前面一篇文章iOS底层原理--类的结构分析中,我们已经对类的底层原理进行了分析。得出了以下结论:
- 对象的
isa
指针质量的时候类,类的isa
指向的是元类(Meta Class),元类最终指向了根元类(NSObject) -
class
在底层是一个objc_class
类型的结构体,而objc_class
又是继承自objc_object
,objc_object
中包含了isa
,所以任何类都有isa
指针。 -
objc_class
中有个class_data_bits
类型的结构体,通过class_data_bits
的data
我们可以拿到对象的属性、方法、协议等。
方法的归属问题
我们在LGPerson
类中添加2个方法,如下:
//
// LGPerson.h
// KCObjc
//
// Created by Cooci on 2020/7/24.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
+(void) sayHello;
-(void) sayHi;
@end
NS_ASSUME_NONNULL_END
一个实例方法sayHi
,一个类方法sayHello
。我们来来看看在底层的情况。
- 我们获取
LGPerson
的地址
- 拿出首地址
0x0000000100002238
平移32位 得到0x0000000100002258
,得到bits
- 获取
data()
- 获取
class_rw_t
- 获取方法列表
methods()
- 获取
methods()
的数据
我们可以看到以下信息:- 对象会自动帮我们生成
getter
、setter
方法 -
sayHi
存在在类中
- 对象会自动帮我们生成
sayHello
并没有找到?我们知道在底层中,并没有类方法和实例方法的区别,所以只是方法存在的位置不同而已,那么类方法会不会存在在元类中呢。实验一下:
可以看到,
sayHello
方法存在在元类中,类方法就相当于元类的实例方法。
接下来,我们看2道面试题
面试题一
我们有一个类LGPerson
//
// LGPerson.h
// 002-类方法归属分析
//
// Created by cooci on 2020/9/12.
// Copyright © 2020 cooci. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end
NS_ASSUME_NONNULL_END
我们要进行如下操作:
void lgInstanceMethod_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));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
这个方法顾名思义就是去找类的实例方法,pClass
为类对象,而metaClass
为元类。
- 第一条为
类
中有没有sayHello
方法,因为sayHello
是实例方法
,所以是有的,此时可以打印方法地址。 - 第二条为
元类
中有没有sayHello
方法,因为sayHello
是实例方法
,所以是没有的,此时不可以打印方法地址。 - 第三条为
类
中有没有sayHappy
方法,因为sayHappy
是类方法
,所以是没有的,此时不可以打印方法地址。 - 第三条为
元类
中有没有sayHappy
方法,因为sayHappy
是类方法
,所以是有的,此时可以打印方法地址。
最终结果如下所示。
面试题二
接下来还是刚刚的类,我们看看另一个操作
void lgClassMethod_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));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
这个方法顾名思义就是去找类的类方法,pClass
为类对象,而metaClass
为元类。
- 第一条为
类
中有没有sayHello
方法,因为sayHello
是实例方法
,所以是没有的,此时不可以打印方法地址。 - 第二条为
元类
中有没有sayHello
方法,因为sayHello
是实例方法
,所以是没有的,此时不可以打印方法地址。 - 第三条为
类
中有没有sayHappy
方法,因为sayHappy
是类方法
,所以是没有的,此时可以打印方法地址。 - 第四条,照理说应该是没有地址的,因为
sayHappy
是类方法
,也就是说是元类
的实例方法
。
那么我们来看打印结果
但是,和我们预想的不一样,为啥印元类的的类方法呢?我们来看看源码。
class_getClassMethod
我们来看看class_getClassMethod
的源码里到底做了啥
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
-
class_getClassMethod
最终会去调用class_getInstanceMethod
方法 - 调用对象是
getMeta()
也就是对象元类
总结class_getClassMethod
的本质就是调用元类的实例方法
那么,我们看看getMeta()
里做了什么?
cls->getMeta()
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
这个方法是获取类的元类
的方法,有两种情况:
- 如果是
元类
,就返回自己 - 如果不是
元类
,那么就返回它的isa
指向的地址,即元类
所以再看这一段
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
由于metaClass
本身就是元类,所以就是在他的里面找实例方法,所以会返回方法的地址。
面试题三
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]]; //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]]; //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]]; //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
return 0;
}
这个面试题就是分析isKindOfClass
和isMemberOfClass
的区别
isKindOfClass
isKindOfClass
中也分为类方法和实例方法
- 类方法
+ (BOOL)isKindOfClass:(Class)cls {
// tcls是第一个参数的元类
// cls是后面对比的类
// tcls->superclass是去找父类
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
该方法就是那当前对象的isa
即元类
和传入的对象进行对比,如果一致则返回YES
,如果不一样则去父类找,如果未找到则返回NO
。
- 实例方法
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
该方法就是那当前对象
的和传入的对象
进行对比,如果一致则返回YES
,如果不一样则去父类
找,如果未找到则返回NO
。
isMemberOfClass
isMemberOfClass
中也分为类方法和实例方法
- 类方法
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
该方法会去查找当前类的元类
和传入的类
是否相等。
- 实例方法
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
该方法会去查找当前对象和传入的对象
是否相等。
所以最终打印结果如下