引言
促使我写这篇文章的主要原因是我发现平时利用的class
方法与object_getClass
函数的返回值竟然是不一样的,这与我之前理解的类的概念一直有所偏差。在查阅了部分资料后,发现对objc
中关于类与对象有了一个新的理解,遂撰此文记录下。
为了更好的阅读体验,推荐到我的博客阅读。
码字不易,各位看官看的喜欢烦请点个赞吧!以示鼓励啊😢
深入了解类与对象
想要搞清楚这两个方法的区别,需要彻底搞清楚objc
中关于类与对象的概念。
objc
中对象与类的概念
objc
中关于对象的定义如下:
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
可见对象是一个含有isa
指针的结构体。那么isa
指向的Class
又是什么呢?
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
原来Class
是一个指向objc_class
结构体的指针,apple的注释写的很清楚,Class
相当于objc
中的类。所以只有我们搞明白了objc_class
结构体到底是什么,也就搞清楚了objc
中关于类的概念。
objc/runtime.h中关于该结构体的定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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;
/* Use `Class` instead of `struct objc_class *` */
虽然下面的一段串内容已经是OBJC2_UNAVAILABLE
了,但依然可以看出一些apple关于class
的定义的倪端:
- 我们注意到,
objc_class
结构体内部也含有一个isa
指针,也就说“类也是有类的”。虽然读起来很拗口,但这也是为什么类有时候也被称为类对象的原因:类其实也是一个对象~ - 下面一段内容含有:类的方法列表(这些方法其实都是对象方法,至于为什么,后面解释),类的成员变量列表,类的
cache
列表,类的协议列表。更重要的是,它还有一个指向Class
的super_class
指针。
接下来,我们来一一讲解这些东西到底都是干嘛的。
-
要想搞清楚下面的概念,首先要知道顶部的
Class isa
的含义。我们之前已经提到过,类也是对象,正是因为这个isa
指针。我们从objc_object
的结构体定义中已经看得很明白:何为对象?不过是一个含有指向Class
的isa
指针罢了。故类不过也是一个对象。 -
看到这如果你没有一个清晰的概念,可能已经糊涂了。没关系,只要搞明白类对象里的
isa
指针指向的是什么,你就明白了一切。先上个图:
我们现在讲的类对象,就是中间的RootClass
,ASuperClass
,AClass
。我们可以清晰的看到,此处的isa
指针指向的是MetaClass
,又译为元类。而所谓元类,就是类对象的类,也可以说是类对象的类对象。
看到这里,你可能已经恍然大悟:对象的isa
指针指向类对象,类对象的isa
指针指向了元类。元类的isa
指针指向根元类。三者统一了objc
关于对象,类的基本概念。
- 讲完了对象,类对象的
isa
指针。让我们回到objc1
中关于那长长的一串定义:
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
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
结合上面的元类概念,我们就能明白,原来苹果设计类对象的用意,就是为了存储对象的某些信息,比如对象方法,类遵守的一些协议,对象的属性,对象的类的父类(拗口。。。)等等。所以我们也就不能理解设计元类的用意:存储类的一些信息,比如类方法就存在元类里。相信读到这里,已经对objc
中的对象和类有了一个很深刻的理解。下面说说我写这篇文章的主要原因:理解object_getClass
函数与class
方法。
理解object_getClass
函数与class
方法。
注:此处需要对runtime
有了解
天天都在用的
class
方法
-
说起
class
方法这个方法,大家必然都不陌生,最常用的大概就是下面这个对象方法:Class aClass = [anObject class];
此方法目的就是返回某个对象的类。说到这里,结合上面的结论,既然类是一个对象,那么给类发送
class
消息会怎么样?会编译不过?还是crash?Clas aClass = [NSObject class];
上面这种写法是完全可行的,但是并没有任何意义。给类对象发送
class
消息,会返回类对象自身。如何验证?
//用作测试的类 @interface testClass : NSObject @end //测试代码 testClass *testObject = [testClass new]; Class testObjectClass = [testObject class]; Class aClass = [testClass class]; NSLog(@"testObjectClass.p == %p",testObjectClass); //object's class NSLog(@"[testClass class].p === %p",aClass);// class's class
最后结果:
2016-08-16 16:21:25.581 总结测试[49724:3045182] testObjectClass.p == 0x100286ed8 2016-08-16 16:21:25.581 总结测试[49724:3045182] [testClass class].p === 0x100286ed8
-
为什么会这样?
- (Class)class OBJC_SWIFT_UNAVAILABLE("use 'anObject.dynamicType' instead"); + (Class)class OBJC_SWIFT_UNAVAILABLE("use 'aClass.self' instead");//[NSObject class] + (Class)class;//[NSProxy class]
点开
class
方法定义我们会发现,其实class
既是类方法,也是对象方法。只不过执行对象方法的时候,会返回自己。代码:+ (Class)class{ return self; } - (Class)class{ return object_getClass(self); }
也就理解为:当调用者是类对象时,
class
方法返回自身;当调用者是对象时,返回object_getClass
函数的返回值。(object_getClass
函数其实返回的是isa
指针指的对象,后面会解释)
强大的
runtime
函数:object_getClass
-
知道了
class
方法的实质,接下来就要好好了解下object_getClass
这个函数了。结合上面的给出的对象、类、元类图我们能猜想出该函数的伪码:Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; }
就是说:该函数返回的是
id
的isa
指向的结构体 -
看到这里,你也许感觉豁然开朗,于是你可能会这样写:
NSNumber *aNumber = [NSNumber numberWithInt:2]; NSLog(@"instance.p === %p",aNumber);//对象地址 NSLog(@"classInstance.p === %p",[NSNumber class]);//类对象地址 class方法获得 NSLog(@"classInstance.pWithGetClassFunc%p",object_getClass(aNumber));//类对象地址 函数获得 NSLog(@"metaClass.p%p",object_getClass(object_getClass(aNumber)));//类对象的isa指针 NSLog(@"metaClass.isa.p%p,",object_getClass(object_getClass(object_getClass(aNumber))));//元类的isa指针 NSLog(@"metaClass.isa.isa.p%p",object_getClass(object_getClass(object_getClass(object_getClass(aNumber)))));//元类的isa指针的isa指针 NSLog(@"metaClass.isa.isa.isa.p%p",object_getClass(object_getClass(object_getClass(object_getClass(object_getClass(aNumber))))));//元类的isa指针的isa指针的isa指针 然后你会看到如下输出: instance.p === 0xb000000000000022 classInstance.p === 0x10e5022a0 classInstance.pWithGetClassFunc 0x105a6a368 metaClass.p 0x105a6a390 metaClass.isa.p 0x105617198 metaClass.isa.isa.p 0x105617198 metaClass.isa.isa.isa.p 0x105617198
先看第二段,你会发现第一行和第二行的地址不一样,这是因为元类的
isa
指针指向的是根元类,而根元类的isa
指针指向自己。再看第一段,对象的地址和类对象的地址完全不一样。这是因为类对象既不在栈上,也不在堆上。你可以把它理解为单粒。至于为什么是这样,不在本文讨论中。有兴趣的同学可以自己查阅资料。
然后我们会发现
[NSNumber class]
获得的类对象与函数object_getClass
获得的类对象地址居然不一样?!what f -- k
没关系,耐着性子这样查:
Class class1 = [NSNumber class]; Class class2 = [aNumber class]; Class class3 = object_getClass(aNumber);
打个断点,在控制台依次p出每个class:
(lldb) p class1 (Class) $0 = NSNumber (lldb) p class2 (Class) $1 = __NSCFNumber (lldb) p class3 (Class) $2 = __NSCFNumber
原来NSNumber
是类簇。这里还是简单的说说类簇的概念吧:一个父类有好多子类,父类在返回自身对象的时候,向外界隐藏各种细节,根据不同的需要返回的其实是不同的子类对象,这其实就是抽象类工厂的实现思路,iOS最典型的就是NSNumber
。
码字不易,各位看官看完如有收获烦请点个赞吧!以示鼓励啊😢
至此,终于写完了这篇文章。希望对大家有所帮助吧~~
网友评论