最开始让我有疑问的是在使用类方法,在控制台下仍然发现Class对应的有地址,类没有初始化,为什么会有地址?这个地址是谁的地址?
这个问题有点复杂,刨根问底,我查阅OC中的类的实现:
-
Class是一个指向objc_class(类)结构体的指针,而id是一个指向objc_object(对象)结构体的指针。
-
objec_object(对象)中isa指针指向的类结构称为objec_class(该对象的类),其中存放着普通成员变量与对象方法 (“-”开头的方法)。
-
objec_class(类)中isa指针指向的类结构称为metaclass(该类的元类),其中存放着static类型的成员变量与static类型的方法 (“+”开头的方法)。
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
isa指针.png
当我们调用某个对象的对象方法时,它会首先在自身isa指针指向的objc_class(类)的methodLists中查找该方法,如果找不到则会通过objc_class(类)的super_class指针找到其父类,然后从其methodLists中查找该方法,如果仍然找不到,则继续通过 super_class向上一级父类结构体中查找,直至根class;
值得注意的是
-
所有的metaclass(元类)中isa指针都是指向根metaclass(元类),而根metaclass(元类)中isa指针则指向自身。
-
根metaclass(元类)中的superClass指针指向根类,因为根metaclass(元类)是通过继承根类产生的。
-
当我们调用某个类方法时,它会首先通过自己的isa指针找到metaclass(元类),并从其methodLists中查找该类方法,如果找不到则会通过metaclass(元类)的super_class指针找到父类的metaclass(元类)结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查 找,直至根metaclass(元类);
-
这里有个细节就是要说运行的时候编译器会将代码转化为objc_msgSend(obj, @selector (makeText)),在objc_msgSend函数中首先通过obj(对象)的isa指针找到obj(对象)对应的class(类)。在class(类)中先去cache中通过SEL(方法的编号)查找对应method(方法),若cache中未找到,再去methodLists中查找,若methodists中未找到,则去superClass中查找,若能找到,则将method(方法)加入到cache中,以方便下次查找,并通过method(方法)中的函数指针跳转到对应的函数中去执行。
object_class结构体中有一个isa指针。一个类实例的isa指针指向的自己本身这个类对象,类对象的isa指向自己元类对象。
元类对象(metaclass object)中存储的是关于类的信息(类的版本,名字,类方法等)。要注意的是,类对象(class object)和元类对象(metaclass object)的定义都是objc_class结构。所以很明显系统需要开辟内存存储类的信息。这个结构就是元类对象,显而易见这个地址也是元类对象的地址。
object_getClass()就是顺着isa的指向链找到对应的类,我们一会要验证这个isa的指向链是否与上面图中是一致的,就是用这个方法。
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
这是NSObject类里实例方法class与类方法class的实现,这里再强调一下:类方法是在meta class里的,类方法就是把自己返回,而实例方法中是返回实例isa的类,我们要验证这个isa的指向链的时候不能用这种方法。
对于普通对象而言比如我们自定义一个Person:
#import <objc/runtime.h>
#import "Person.h"
Person *obj = [Person new];
NSLog(@"instance :%p", obj);
NSLog(@"class :%p", object_getClass(obj));
NSLog(@"---------------------------------------------");
NSLog(@"class :%p", [obj class]);
Log输出:
TimerDemo[1718:248402] instance :0x7fc792530f20
TimerDemo[1718:248402] class :0x10ae0e178
TimerDemo[1718:248402] ---------------------------------------------
TimerDemo[1718:248402] class :0x10ae0e178
1.我们发现调用class方法的方式不能得到isa的指向链,但是第一次调用是正确的(class的输出都是0x10ae0e178),为什么? 我们第一次调用的class是实例方法,会返回isa的类,object_getClass调用返回的是isa指向的对象就是类对象本身,[obj class]是类方法,返回的也是本身,所以还是0x10ae0e178,以后无论怎么调用都是执行的类方法,返回的都是本身,所以,用class方法是得不到isa指向链的。
2.从打印结果我们能看到,类也是对象,meta类也是对象,都占有一块内存,而且我们会发现类对象、meta类对象、root meta类对象的指针都是用9位16进制数表示,而实例对象是用12位16进制数表示(这里用的是64位模拟器),为什么这些类对象的指针位数少?。
但是如果这个类是Apple自建类就有问题了?
NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];
NSLog(@"instance :%p", timer1);
NSLog(@"class :%p", object_getClass(timer1));
NSLog(@"meta class :%p", object_getClass(object_getClass(timer1)));
NSLog(@"[NSTimer class]:%p", [NSTimer class]);
Log输出:
TimerDemo[1745:255746] instance :0x7fee8bc7a810
TimerDemo[1745:255746] class :0x10ece02c0
TimerDemo[1745:255746] meta class :0x10ece02e8
TimerDemo[1745:255746] [NSTimer class]:0x10ecdfe38
对于NSTimer这个类,[NSTimer class]返回应该是类对象,object_getClass(timer1)返回的也应该是类对象,为什么指针不同?
答案非常简单,两个字:类簇,我们熟悉的类簇是NSNumber,NSArray,NSDictionary、NSString...这说明大多数的OC类都是类簇实现的(连NSTimer也不放过),也说明为什么我们Person类是正常的,因为它不是类簇实现的。
这里还是简单的说说类簇的概念吧:一个父类有好多子类,父类在返回自身对象的时候,向外界隐藏各种细节,根据不同的需要返回的其实是不同的子类对象,这其实就是抽象类工厂的实现思路,iOS最典型的就是NSNumber。
NSNumber *intNum = [NSNumber numberWithInt:1];
NSNumber *boolNum = [NSNumber numberWithBool:YES];
NSLog(@"intNum :%@", [intNum class]);
NSLog(@"boolNum:%@", [boolNum class]);
---------------------------------------------
TimerDemo[1018:35735] intNum :__NSCFNumber
TimerDemo[1018:35735] boolNum:__NSCFBoolean
有人可能要问,如何证明__NSCFTimer就是NSTimer的子类,如何证明类簇是真的?其实很简单:
NSLog(@"[NSTimer class] :%p", [NSTimer class]);
NSLog(@"class_getSuperClass:%p", class_getSuperclass([timer1 class]));
----------------------------------------------------
TimerDemo[1038:39690] [NSTimer class] :0x109ee4e38
TimerDemo[1038:39690] class_getSuperClass:0x109ee4e38
网友评论