ios中对象有几种?
实例对象:isa 指针、具体的成员变量的值(自己的公开,私有变量+父类的公开,私有变量),比如age=10,这里存的是10,
类对象:isa、superclass、类属性信息(用class修饰的属性)、对象方法、协议、成员变量列表,这里存的是变量的名字,比如age,也是既有自己的,也有父类的
元类对象:isa、superclass、类方法……
类对象也是可以有属性的 , 比如 NSURLSession就有一个class修饰的属性,相当于单例,其他的还有NSFileManager的defaultManager之类的
isa 指向?superclass 指向?
方法说明
1.Class objc_getClass(const char *aClassName)
- 1> 传入字符串类名
- 2> 返回的都是对应的类对象,因为传入的是字符串,
2.Class object_getClass(id obj)
- 1> 传入的obj可能是instance对象、class对象、meta-class对象
- 2> 返回值
a) 如果是instance对象,返回class对象
b) 如果是class对象,返回meta-class对象
c) 如果是meta-class对象,返回NSObject(基类)的meta-class对象
总结就是返回isa指向的对象
3 . class方法
NSObject *obj1 = [[NSObject alloc] init];
Class objClass1 = [obj1 class];
Class objClass2 = [NSObject class];
//class 方法返回的一直是class对象,类对象,而不是元类对象
Class objClass3 = [[NSObject class] class];
Class objClass4 = object_getClass(obj1);
NSLog(@"%p-%p-%p-%p",objClass1,objClass2,objClass3,objClass4);
上述都是同一个类对象,打印出的地址相同,每个类在内存中有且只有一个class对象
源码分析
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
inline Class
objc_object::getIsa()
{
if (isTaggedPointer()) {
uintptr_t slot = ((uintptr_t)this >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
return ISA();
}
类的本质
类的本质是结构体
#objc_class:objc_object
struct objc_class : objc_object {
// Class ISA;
Class superclass;
//方法缓存
cache_t cache; // formerly cache pointer and vtable
//用于获取具体类信息
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
// 诸多方法
}
#class_rw_t
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;//只读类表
method_array_t methods;//方法列表
property_array_t properties;//属性列表
protocol_array_t protocols;//协议列表
// 诸多方法
}
#class_ro_t
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;//instance对象占用的内存空间
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;//类名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成员变量列表
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
#objc_object
struct objc_object {
private:
isa_t isa;//类的isa指针是私有的
public:
// 诸多方法
}
结构体结构
右边的值
第一位index,代表是否开启isa指针优化 。
- 0 表示 isa_t 没有开启指针优化,不使用 isa_t 中定义的结构体。访问 objc_object 的 isa 会直接返回 isa_t 结构中的
cls
变量,cls
变量会指向对象所属的类的结构,在 64 位设备上会占用 8byte。 - 1 表示 isa_t 开启了指针优化,不能直接访问 objc_object 的 isa 成员变量 (因为 isa 已经不是一个合法的内存指针了,而是一个 Tagged Pointer ),从其名字 nonpointer 也可获知这个 isa 已经不是一个指针了。但是 isa 中包含了类信息、对象的引用计数等信息,在 64 位设备上充分利用了内存空间。
has_assoc : 对象含有或者曾经含有关联引用,没有关联引用的可以更快地释放内存 . 当对象有关联引用时,释放对象时需要做额外的逻辑。关联引用就是我们通常用 objc_setAssociatedObject 方法设置给对象的。
has_cxx_dtor : 表示该对象是否有 C++ 或者 Objc 的析构器. 如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象。
shiftcls : 类的指针。arm64架构中有33位可以存储类指针。
源码中isa.shiftcls = (uintptr_t)cls >> 3;将当前地址右移三位的主要原因是用于将 Class 指针中无用的后三位清除减小内存的消耗,因为类的指针要按照字节(8 bits)对齐内存,其指针后三位都是没有意义的 0。具体可以看从 NSObject 的初始化了解 isa这篇文章里面的shiftcls分析。
magic : 判断对象是否初始化完成,在arm64中0x16是调试器判断当前对象是真的对象还是没有初始化的空间。在 x86_64 中该值为 0x3b。
weakly_referenced : 对象被指向或者曾经指向一个 ARC 的弱变量,没有弱引用的对象可以更快释放
deallocating : 对象是否正在释放内存
has_sidetable_rc : 判断该对象的引用计数是否过大,如果过大则需要其他散列表来进行存储。
extra_rc : 存放该对象的引用计数值减一后的结果。对象的引用计数超过 1,会存在这个这个里面,如果引用计数为 10,extra_rc的值就为 9。
class_ro_t是一个指向常量的指针,存储来编译器决定了的属性、方法和遵守协议。rw-readwrite (可读写),ro-readonly(只读) ,
在编译期类的结构中的 class_data_bits_t *data指向的是一个 class_ro_t *指针 , 在运行时调用 realizeClass方法,会做以下3件事情:
-
从 class_data_bits_t调用 data方法,将结果从 class_rw_t强制转换为 class_ro_t指针
-
初始化一个 class_rw_t结构体
-
设置结构体 ro的值以及 flag
最后调用methodizeClass方法,把类里面的属性,协议,方法都加载进来。
cache_t的具体实现
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
}
typedef unsigned int uint32_t;
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
typedef unsigned long uintptr_t;
typedef uintptr_t cache_key_t;
struct bucket_t {
private:
cache_key_t _key;
IMP _imp;
}
根据源码,我们可以知道cache_t中存储了一个bucket_t的结构体,和两个unsigned int的变量。
mask:分配用来缓存bucket的总数。
occupied:表明目前实际占用的缓存bucket的个数。
bucket_t的结构体中存储了一个unsigned long和一个IMP。IMP是一个函数指针,指向了一个方法的具体实现。
cache_t中的bucket_t *_buckets其实就是一个散列表,用来存储Method的链表。
Cache的作用主要是为了优化方法调用的性能。当对象receiver调用方法message时,首先根据对象receiver的isa指针查找到它对应的类,然后在类的methodLists中搜索方法,如果没有找到,就使用super_class指针到父类中的methodLists查找,一旦找到就调用方法。如果没有找到,有可能消息转发,也可能忽略它。但这样查找方式效率太低,因为往往一个类大概只有20%的方法经常被调用,占总调用次数的80%。所以使用Cache来缓存经常调用的方法,当调用方法时,优先在Cache查找,如果没有找到,再到methodLists查找。
struct method_t {
SEL name;
const char *types;
IMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
方法method的定义如上。里面包含3个成员变量。SEL是方法的名字name。types是Type Encoding类型编码,表示函数的类型 , 函数的返回值类型 和 参数类型。
IMP是一个函数指针,指向的是函数的具体实现。在runtime中消息传递和转发的目的就是为了找到IMP,并执行函数。
最后 总结一张图
能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?
1.不能向编译后得到的类增加实例变量
2.能向运行时创建的类中添加实例变量
解释:
1.编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或class_setWeaklvarLayout来处理strong weak引用.所以不能向存在的类中添加实例变量
2.运行时创建的类是可以添加实例变量,调用class_addIvar函数.但是的在调用objc_allocateClassPair之后,objc_registerClassPair之前,原因同上.