类和对象
OC中的类
OC中.类基于C/C++的结构体.
通过查看NSObject的类定义,可以看到内部有一个Class isa
的成员变量. 从Apple开放的objc源码来看,可以发现,Class类型是一个结构体指针
继续往下找.objc_class
的内部实现,可以发现objc_class
是继承于objc_object
的结构体.而objc_object
内部.只存放了isa
变量.
isa
这里需要知道.64位系统前.isa
是个普通指针.存放着类对象和元类对象地址值
64位系统中,isa
是一个union(共用体)
.这里涉及到共用内存以及位域的概念.
对于共用体,个人理解不深…就不乱分析误导别人了.相关的内容,依据我个人的理解.我在对应成员后都加上注释了.直接获取isa的地址.是拿不到正确地址的.需要与 ISA_MASK
做位运算&
才能得到真正的isa地址.
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
struct {
uintptr_t nonpointer : 1; /*nonpointer 值为0代表普通指针.
存储Class, Meta-Class对象的内存地址
1代表优化过的union*/
uintptr_t has_assoc : 1; // 是否有关联对象,影响释放效率
uintptr_t has_cxx_dtor : 1; // 是否有析构函数,影响释放想绿
uintptr_t shiftcls : 33; /* 存放地址值.存放着Class,Meta-Class的地址
&ISA_MASK就是取的这个成员 根据与运算的规则. 类对象地址最后三位都是0*/
uintptr_t magic : 6; // 分辨对象是否完场初始化
uintptr_t weakly_referenced : 1; // 是否有弱引用
uintptr_t deallocating : 1; // 是否正在释放
uintptr_t has_sidetable_rc : 1; // 引用计数是否过大,会指向引用表
uintptr_t extra_rc : 19; // 引用计数
};
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
复制代码
class_rw_t
- class_ro_t (里面存放的都是一维数组.只读.包含类初始内容)
- method_list_t * baseMethodList;
- protocol_list_t * baseProtocols;
- const ivar_list_t * ivars;
- property_list_t *baseProperties;
- method_array_t methods;
- property_array_t properties;
- protocol_array_t protocols;
class_rw_t
中.存放了类的 成员变量, 协议, 属性以及方法. 其中.class_ro_t
中存放的是类的初始内容.这些内容不可被更改.而 methods, properties, protocols
都是二位数组,可读可写.包含类的初始,以及分类的内容.
之所以设置成二维数组,是为了实现动态添加相应的功能.
本类的方法一开始都放在class_ro_t
中,当运行程序时,分类中的数据与class_ro_t
的数据会合并放到class_rw_t
中.
关于分类的详细.由于篇幅过长.后续会在其他的文章进行分析.
关于method_t
.应该会在消息发送机制(objc_msgsend)
里面进行补充
总结
OC中的类.其实是一个结构体指针.指向的结构体类型为 objc_class
.其内部存放了isa
. 以及类成员变量, 协议, 属性以及方法.
OC中的对象
实例对象(Instace)
实例对象,顾名思义就是类的实例对象.当一个类的实例对象在堆中alloc时.内存中存放的是实例对象的isa以及成员变量.至于方法.是通过isa找到对应类对象.再寻找对应的方法进行调用
类对象(Class)
OC的类对象,其实我们上面已经讲过了.类对象存放了isa.成员变量列表.属性列表.方法列表.协议列表.但是值得注意的一点是,类对象中存放的方法为实例方法
元类对象(Metaclass)
元类对象.是类对象的元类对象.元类对象的结构与类对象基本一致.但是存放的是类的类方法
为什么说类对象存放的是实例方法.元类对象存放的是类方法.我们通过把OC代码编译成c++可以得到答案.
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp
复制代码
从编译得到的C++代码中可以看到. OC调用方法,其实是调用的 objc_msgSend().
看看objc_msgSend
的部分代码,不难看出.调用方法时.会先判断传入的是否为nil
,如果不是.
找到传入对象的isa
.然后找到对应的类中寻找方法.
isa指向
对象的isa
===> 类
类的isa
===> 元类(metaClass)
元类(metaClass)的isa
===>父类 ===> RootMetaClass(根元类)
RootMetaClass(根元类)的isa
===> RootMetaClass(根元类)
类对象的superClass
===> 父类 ===> rootClass(根类)
===> nil
元类的superClass
==> 父元类 ===> metaRootClass (根元类)
===> rootClass(根类)
===> nil
方法调用顺序(不涉及消息转发)
-
调用实例对象方法时
-
通过isa找到对象的类,然后查找类中的方法列表,
-
如果找不到,通过superClass找到父类
-
最终到根类.如果都找不到.程序崩溃
(unrecognize selector)
-
-
调用类方法时
-
通过isa找到元类,查找类方法列表
-
层层向上.到根元类.
-
最终到根类.根元类
isa
指向根类.都找不到.崩溃
-
思考题:上述代码会不会崩溃
@interface Test : NSObject
+ (void)test;
@end
@implementation Test
@end
@interface NSObject (Test)
- (void)test;
@end
@implementation NSObject (Test)
- (void)test {
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[Test test];
}
return 0;
}
复制代码