1、类存在几份?
由于类的信息在内存中永远只存在一份,所以 类对象只有一份,同样,元类对象也只有一份,在lldb
中po
类对象和元类对象的地址会输出相同的类名,因为po
会调用类的description
方法。
2、objc_object 与 对象的关系
objc_object
是OC类的c/c++
实现,没有直接的联系,编译器会在编译阶段将OC语法的类转译为c/c++
的objc_object
结构体实现,objc_object
结构体是OC类的底层模板。通过typedef struct objc_object *id;
定义可以看到id
这个可以指向任意OC类关键字实际是指向objc_object
结构体的指针。
3、什么是 属性 & 成员变量 & 实例变量 ?
- 属性在OC中是通过
@property
开头定义在类的头文件中,编译器会给这样声明的属性添加set
和get
方法,另外在类内部也可以通过_ivarName
的方式,即下划线加属性名的方式直接访问属性,而不必使用点语法,这样做效率更高,减少了set
和get
方法的消息转发。 - 成员变量在OC的类
@implement{}
中定义的,仅供类内部访问的变量,编译器不会生成set
和get
方法。 - 实例变量是对象类型的变量,是需要实例化的变量,是一种特殊的成员变量,例如 NSObject、UILabel、UIButton等。
4、如何证明元类中存放着类方法
//定义Person类
@interface Person : NSObject
- (void)sayHi;
+ (void)sayHaHa;
@end
//添加类方法和实例方法
#import "Person.h"
@implementation Person
- (void)sayHi{
NSLog(@"Person say : Hi!!!");
}
+ (void)sayHaHa{
NSLog(@"Person say : HaHa!!!");
}
@end
通过运行时方法class_copyMethodList
可以获取一个类的实例的方法列表:
//打印方法名
void objc_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));
CHLog(@"Method, name: %@", key);
}
free(methods);
}
在main
中运行以上代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person alloc];
Class pClass = object_getClass(person);
objc_copyMethodList(pClass);
}
return 0;
}
输出结果:
在
objc_copyMethodList
法中我们传入了Person
类对象,所以只会输出类的实例方法sayHi
,符合预期,再看下面的方法输出:
void instanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHi));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHi));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHaHa));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHaHa));
CHLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
这个方法分别针对Person
类和元类调用class_getInstanceMethod
方法,看看打印结果:
可以看到共打印了4个方法的地址,只有
method1
和method4
打印出了内容,其余都是0x0
说明没找到对应的方法,这就很好的证明了实例方法sayHi
可以在类对象pClass
中找到,而类方法sayHaHa
,只能在metaClass
元类中找到,这4个方法都调用了class_getInstanceMethod
,从方法名中可以判断是获取实例方法,进而说明对元类调用获取实例方法,就相当于获取一个类的类方法,因为类在内存中也是一个特殊的实例,元类中存储的就是这个特殊实例的实例方法。
void classMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHi));
Method method2 = class_getClassMethod(metaClass, @selector(sayHi));
Method method3 = class_getClassMethod(pClass, @selector(sayHaHa));
Method method4 = class_getClassMethod(metaClass, @selector(sayHaHa));
CHLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
将上面方法中调用的class_getInstanceMethod
换成class_getClassMethod
,分别对类和元类进行测试,看看打印结果:
打印结果只有
method3
和method4
打印出了地址,分析之前看看class_getClassMethod
的源码实现:
//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
//获取一个类的元类,如果本身是元类,就直接返回自己,否则返回类的ISA()找到元类
Class getMeta() {
if (isMetaClass()) return (Class)this;
else return this->ISA();
}
-
method1
是传入了Person
类本身和实例方法sayHi
,把参数带入到class_getClassMethod
中分析,经过第一个if (!cls || !sel) return nil;
条件判断,最终调用了class_getInstanceMethod
传入了Person
类的元类,sel
参数是sayHi
,这个方法在Person
元类中是找不到的,因为它是一个实例方法,存放在类对象中,所以会返回0x0
,找不到这个方法。 -
method2
是传入了Person
类的元类和实例方法sayHi
,把参数带入到class_getClassMethod
中分析,经过条件判断,最终调用了class_getInstanceMethod
传入了Person
类的元类,sel
参数是sayHi
,元类的getMeta()
方法会返回自己,所以还是在Person
元类中查找sayHi
,因为它是一个实例方法,存放在类对象中,元类中没有这个方法,返回0x0
,找不到这个方法。 -
method3
是传入了Person
类本身和类方法sayHaHa
,把参数带入到class_getClassMethod
中分析,最终调用了class_getInstanceMethod
传入了Person
类的元类,sel
参数是sayHaHa
,这个方法在Person
元类中是存在的,因为sayHaHa
是一个类方法,返回方法地址0x100003148
。 -
method4
是传入了Person
类的元类和类方法sayHaHa
,把参数带入到class_getClassMethod
中,最终调用了class_getInstanceMethod
传入了Person
类的元类,sel
参数是sayHaHa
,元类的getMeta()
方法会返回自己,所以还是在Person
元类中查找类方法sayHaHa
,可以找到方法并返回方法地址0x100003148
。
为什么元类的getMeta()
会返回自己,这个是为了防止无限递归,因为对一个类调用class_getClassMethod
会顺着类的isa
找到类的元类,而对元类调用class_getClassMethod
的话,如果不返回元类自身,还是沿着元类的isa
查找,最终会找到根元类,所有元类的isa
都指向根元类,而根元类的isa
指向了自己,就会在这里形成死循环,为了打破这个循环,就在元类的getMeta()
方法中直接返回自己终止isa
的循环。
再来看看下面的方法:
void imp_ClassToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
// - (void)sayHi;
// + (void)sayHaHa;
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHi));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHi));
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHaHa));
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHaHa));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
结果打印:0x100001d10-0x7fff6a7d6580-0x7fff6a7d6580-0x100001d40
,四个imp
都会打印出来,不太符合直觉,从元类里面获取实例方法的实现也拿到了地址,从类里面获取类方法的实现也能拿到地址,看看class_getMethodImplementation
的源码实现:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
//查找方法实现
imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
//没有找到,则进行消息转发
if (!imp) {
return _objc_msgForward;
}
return imp;
}
从源码中可以知道由于消息转发机制的处理,即使类或者元类中找不到对应的类方法或者实例方法,最终通过消息转发,也能获取到对应方法的实现!
5、分析下面代码中iskindOfClass 、 isMemberOfClass 的打印结果:
void isKindAndisMember() {
//-----调用类的 iskindOfClass & isMemberOfClass
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]]; //
BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]]; //
BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]]; //
NSLog(@"\nClass isKindAndisMember\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
//------调用实例的iskindOfClass & isMemberOfClass
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //
BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]]; //
BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]]; //
NSLog(@"\nInstance isKindAndisMember\n re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
}
看下打印结果:
分析前,先看看
iskindOfClass
和isMemberOfClass
的源码:
//isKindOfClass类方法
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//isKindOfClass实例方法
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
//isMemberOfClass类方法
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
//isMemberOfClass实例方法
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
下面开始分析:
-
res1
=[(id)[NSObject class] isKindOfClass:[NSObject class]];
对NSObject
调用isKindOfClass
,首先会调用+ (BOOL)isKindOfClass:(Class)cls
类方法,其过程是for
循环中tcls
初始等于self->ISA()
,也就是NSObject
的元类,第一次循环相当于比较NSObject
类和NSObject
的元类是否地址相同,显然不相等,进入下一循环,此时tcls
等于它的父类tcls->superclass
,NSObject
元类就是根源类,根源类的superclass
指向NSObject
类,此时tcls
就变成NSObject
类,再次比较tcls
和传入的cls
,这两个值现在都是NSObject
类,所以是相等的,返回YES
, 所以res1
打印结果就是1
。 -
re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
对NSObject
调用isMemberOfClass
,会调用+ (BOOL)isMemberOfClass:(Class)cls
类方法,最终比较的是self->ISA()
和传入的cls
,也就是NSObject
的元类和NSObject
类,显然实不相等的,所以返回NO
,re2
打印为0
-
re3 = [(id)[Person class] isKindOfClass:[Person class]];
对Person
调用isKindOfClass
,首先会调用+ (BOOL)isKindOfClass:(Class)cls
类方法,for
循环中tcls
初始等于self->ISA()
,也就是Person
的元类,相当于比较Person
类和Person
的元类是否地址相同,不相等,进入下一循环,此时tcls
等于它的父类tcls->superclass
,也就是NSObject
元类,是根源类,和Person
类也不相等,继续向上寻找superclass
,下一个循环中tcls
就变成了NSObject
类,根源类的superclass
是NSObject
类,和Person
类也不相等,继续再向上找superclass
,NSObject
类的superclass
是nil
,和Person
类也不相等,最后由于tcls
变成了nil
循环终止,返回NO
,所以打印re3
也是0
-
re4 = [(id)[Person class] isMemberOfClass:[Person class]];
对Person
调用isMemberOfClass
,会调用Person
类的+ (BOOL)isMemberOfClass:(Class)cls
类方法,最终比较的是self->ISA()
和传入的cls
,也就是Person
的元类和Person
类,显然实不相等的,所以返回NO
,re4
打印为0
-
re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
对NSObject
的实例调用isKindOfClass
,会调用NSObject
类的- (BOOL)isKindOfClass:(Class)cls
实例方法,for
循环中tcls
初始等于[self class]
,也就是NSObject
类,传入的参数cls
是[NSObject class]
,也是NSObject
类,所以比较tcls
和cls
是相等的,返回YES
,打印re5
是1
-
re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
对NSObject
的实例调用isMemberOfClass
,会调用NSObject
类的- (BOOL)isMemberOfClass:(Class)cls
实例方法,比较的是[self class]
和传入的[NSObject class]
是否相等,显然是相等的,都是NSObject
类,打印re6
是1
-
re7 = [(id)[Person alloc] isKindOfClass:[Person class]];
对Person
的实例调用isKindOfClass
,会调用Person
类的- (BOOL)isKindOfClass:(Class)cls
实例方法,for
循环中tcls
初始等于[self class]
,也就是Person
类,传入的参数cls
是[Person class]
,也是Person
类,所以比较tcls
和cls
是相等的,返回YES
,打印re7
是1
-
re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];
对Person
的实例调用isMemberOfClass
,会调用Person
类的- (BOOL)isMemberOfClass:(Class)cls
实例方法,比较的是[self class]
和传入的[Person class]
是否相等,显然是相等的,都是Person
类,打印re8
是1
坑点:
上面的分析似乎顺利成章,看不出破绽,都是对着isKindOfClass
和isMemberOfClass
的代码实现一步一步分析的,而且分析的结果也是正确的,但是如果我们用断点调试的话就会发现这里面是有坑的:
分别在以上位置下断点,单步运行就会发现不论是实例方法
isKindOfClass
还是类方法isKindOfClass
都没进到对应的断电中,而isMemberOfClass
都可以进入对应的方法,这个结果是不是很意外,难道我们上面分析的isKindOfClass
流程都是错的吗,系统没有进入对应的isKindOfClass
方法,那最终调用的又是那个方法呢?用汇编方式来看看最终调用的方法是什么:
如上图在
Debug
菜单选择Always Show Disassembly
显示整个方法的汇编代码:
从图中标记
1
的红框可以看到,实际的isKindOfClass
方法调用已经变成了objc_opt_isKindOfClass
,而标记2
的红框显示isMemberOfClass
依然是消息转发objc_msgSend
方式去调用,所以isMemberOfClass
可以进入对应的方法断点,而isKindOfClass
已经直接调用objc_opt_isKindOfClass
的实现了,这里应该是llvm做的编译优化。在runtime
源码中搜索一下objc_opt_isKindOfClass
方法看看是否能找到这个方法:
很容易就搜索到了,声明在
objc-internal.h
中,实现在NSObject.mm
中,点击查看方法实现:
// Calls [obj isKindOfClass]
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
for (Class tcls = cls; tcls; tcls = tcls->superclass) {
if (tcls == otherClass) return YES;
}
return NO;
}
#endif
return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}
可以看到最上面的注释中已经说明了这个就是调用[obj isKindOfClass]
方法最终所执行的实现,在这个方法中打断点可以看到,不论是调用实例的isKindOfClass
还是类的isKindOfClass
,都会进入这个方法,这个方法和之前我们认为要走的+ (BOOL)isKindOfClass:(Class)cls
和- (BOOL)isKindOfClass:(Class)cls
的实现基本是一致的,略有不同,分析一下执行过程:
-
re1
:re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
进入objc_opt_isKindOfClass(id obj, Class otherClass)
方法后,obj
是NSObject
类对象,后面然后通过Class cls = obj->getIsa();
取得NSObject
类的元类,之后进入for
循环,这里就和+ (BOOL)isKindOfClass:(Class)cls
的流程一致了,最终通过根源类的superclass
判断相等
同理,re3 = [(id)[Person class] isKindOfClass:[Person class]];
的过程也是如此,此处就不再赘述了。
再看实例对象调用objc_opt_isKindOfClass(id obj, Class otherClass)
的情况: -
re5
:re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
此时objc_opt_isKindOfClass
方法的obj
参数变成了NSObject
的实例,通过Class cls = obj->getIsa();
取得NSObject
类对象,进入for
循环,此时tcls
是NSObject
类对象,另一个参数otherClass
也是NSObject
类对象,这两个必然是相等的,返回YES
, 所以re5
打印是1