本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
前言
NSObject对象是iOS开发者都很熟悉的对象,它几乎是所有对象的根类。在任何.m文件中输入以下代码:
NSObject
点击NSObject
跳转到其定义文件,发现如下声明:
@interface NSObject {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
其中#pragma clang diagnostic push
用于去除警告,因此,我们能发现NSObject对象只有一个Class
类型的成员变量:isa
。
那么:
- 什么是
isa
- 什么是
Class
类型,与class
方法有何区别
这篇文章将要给大家揭晓该问题。
我们知道任何一个类都有 class
方法比如:
[NSObject class];
当然还有superclass
方法:
[NSObject superclass];
更多和clas相关的方法列举如下:
- (BOOL)isKindOfClass:(Class)aClass;
- (BOOL)isMemberOfClass:(Class)aClass;
这两个方法相信大家都不陌生,只不过这两个方法位于@property NSObject中,但NSObject
中还是有其实现的。所以我们有理由相信,NSObject
中的成员变量isa
是有特殊含义的,点击改成员变量的类型Class
我们可以看到其定义:
typedef struct objc_class *Class;
继续点击objc_class
:
struct objc_class : objc_object {
//这里省略成员变量以及方法...
}
再次点击objc_object
:
struct objc_object {
private:
isa_t isa;
//这里省略成员变量以及方法...
}
层次有点深,但大家只关注其结构即可:
Class
本质是一个结构体。
关于结构体,大家应该都有所了解,这里再做个复习吧:
C语言和C++都支持结构体,只是C++的结构体基本上和类没有区别。以下是摘自知乎某答主:
结构体和类的区别
本质上来说结构体与类是同一个东西,可是默认情况下基于可读性的原因还是加一些区分:
结构体就只含数据成员和构造函数、析构函数,尽可能保持简单。
类则包含更多的非构造、析构成员函数,概念更大,用来描述普遍意义上的对象类型。
我们有理由相信:NSObject
对象的各个方法,基本上是针对其结构体isa对象的操作。这里我们研究几个我们常用的方法:
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
isMemberOfClass:
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
很简单,判断一下,当前的class方法是否等于参数。
因为self
是NSObjec
t对象,因此我们查看class
方法:
- (Class)class {
return object_getClass(self);
}
点击object_getClass
查看其定义:
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
继续进入方法getIsa:
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
可以发现,越牵扯越深,阅读有点困难了。但大家别着急,我们可以屏蔽
if (!isTaggedPointer()) return ISA();
以下的代码,关于什么是TaggedPointer
,笔者会在后面的文章中分析给大家。因此上面的代码可以先简化成:
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
}
继续研究ISA()
方法:
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
同样去掉暂时不需要我们理解的部分,简化代码如下:
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
至此,我们可以看到class方法最终获取的即是:结构体objc_object
的isa.bits & ISA_MASK
的结果。
那,大家的疑问也会随之而来:
-
inline
关键字作用,为何这里的几个方法实现都在.h
文件中 - 在方法:
objc_object::ISA()
中双冒号的作用。 - objc_object 中的isa又是什么
- isa.bits & ISA_MASK 的含义
inline关键字
用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义。
也就是说,用inline
关键字修饰的是内联函数,内联函数用于替代宏定义。取代宏定义的原因是:
- C中使用define这种形式宏定义的原因是因为,C语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成等一系列的操作,因此,效率很高,这是它在C中被使用的一个主要原因。
- 这种宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。
- 在C++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。
- inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了宏定义的缺点,同时又很好地继承了宏定义的优点。
双冒号
用于表示“域操作符”,例:声明了一个类A,类A里声明了一个成员函数void f(),但没有在类的声明里给出f的定义,那么在类外定义f时,就要写成void A::f(),表示这个f()函数是类A的成员函数。
objc_object 中的isa
之前我们已经写了objc_object
的定义,可以知道,isa其实是一个isa_t
的对象,那isa_t
是什么呢,我们继续看一下它的实现:
union isa_t
{
//这里省略很多变量
}
可以知道,isa_t
是个联合体,也就是说:objc_object
中的isa
其实是个结构体
isa.bits & ISA_MASK 含义
上面我们知道,isa
是个联合体,其内部的属性bits呢?
union isa_t
{
//省略部分方法和属性...
uintptr_t bits;
然后看uintptr_t
实现:
typedef unsigned long uintptr_t;
发现其是个unsigned long
类型,而ISA_MASK
的定义如下:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# else
# error unknown architecture for packed isa
# endif
可知,其实ISA_MASK
还是个数值类型。也就是说判断两个对象是否是同一个class
其实是通过比对objc_object
中的数值计算后得出的结果是否相等得出的。
讲完了 isMemberOfClass
方法,isKindOfClass
方法这里就不多做介绍了,给出其源代码即可:
isKindOfClass:
其实现如下:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
总结
-
Class
类型本质是个结构体,该结构体中存储了该NSObject
中的所有信息。 - 比对两个类是否是同一个类,其实是判断
Class
中的某个数值运算的结果是否相等。
本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime
广告
我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。