目录
- 1.Tagged Pointer(带标记的指针)
- 2.引用计数及weak的实现
- 1.isa
- 2.SideTables
- 3.SideTable
- 4.引用计数表
- 5.弱引用表
- 4.autorelease的实现
- 1.__AtAutoreleasePool
- 2.AutoreleasePoolPage
- 3.自动释放池的释放时机
- 附录
- weak源码
一、Tagged Pointer(带标记的指针)
特点:
- 1.Tagged Pointer专门用来存储小的对象,例如NSNumber和NSDate
- 2.Tagged Pointer指针的值不再是地址了,而是真正的值。所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要 malloc 和 free。
- 3.在内存读取上有着 3 倍的效率,创建时比以前快 106 倍。
实验:
NSNumber *n1 = @1; // 0x38548fccceb58159
NSNumber *n2 = @2; // 0x38548fccceb58259
NSNumber *n3 = @3; // 0x38548fccceb58359
NSNumber *largeNumber = @(0xFFFFFFFFFFFFFFF); // 0x100604460,堆空间,正常的对象
Q:请问以下两段代码的运行结果
代码1会崩溃,代码2正常运行。
-
代码1创建的字符串是一个对象;
setter方法会先对原对象release,然后将新对象retain赋值给成员变量;
并发调用setter方法可能会出现多个线程同时调用release的情况,导致崩溃。 -
代码2创建的字符串是一个Tagged Pointer;
objc_messageSend
内部会判断是不是普通的指针,如果是Tagged Pointer,就直接在指针中取出数值进行赋值。
二、引用计数
1、isa
isa
的位域结构体中的extra_rc
,存储着对象的引用计数。如果引用计数较大位域存储不下的话,会设置has_sidetable_rc
为1,将剩下的引用计数存储在对象对应的引用计数表中。
isa
的位域结构体中的weakly_referenced
,记录对象是否被弱引用指向过。
2. SideTables
Q:为什么不是一个SideTable?
A:在多线程的环境下,多个对象同时操作时,会存在效率问题!
SideTables的本质是一个Hash表:
3. SideTable
Q:为什么用自旋锁?
因为读写操作很快,所以适合用自旋锁。
4.引用计数表
引用计数表(RefountMap)是一个Hash表:它存放了多个对象的信息,每个对象对于一个引用计数size_t。
5.弱引用表
弱引用表(weak_table_t)是一个Hash表:它存放了多个对象的信息,每个对象对应了一个weak_entry_t,里面有被引用的对象和弱引用数组。
四、autorelease的实现
// 源OC代码:
@autoreleasepool {
MJPerson *person = [[[MJPerson alloc]init] autorelease];
}
// 编译后:
{
__AtAutoreleasePool __autoreleasepool;
MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
}
自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage。
1.__AtAutoreleasePool
结构:
使用:
调用push方法,会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址 。
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY 。
2.AutoreleasePoolPage
结构:
-
AutoreleasePoolPage对象之间是通过双向链表的形式连接在一起。
-
每个AutoreleasePoolPage对象的结构是:一部分空间用来存放它内部的成员变量,比如指向下一个page指针、指向下一个page的指针。剩下是一个栈,用来存放autorelease对象的地址。
-
因为是后进先出的栈,所以很容易的实现多个@autorelease嵌套的情况。
Q:AutoreleasePool的实现原理?/ AutoreleasePool的结构
自动释放池的底层实现主要用到两个数据结构是:__AtAutoreleasePool、AutoreleasePoolPage。
AutoreleasePoolPage对象之间以双向链表连接,对象内部出了存放它内部成员变量,剩下的空间就是一个栈,用来存放autorelease对象的地址。
__AtAutoreleasePool用来记录该自动释放池的boundary。方便pop。
3.自动释放池的释放时机
(1)如果被@autoreleasepool包住的话,在大括号结束的时候会释放。
(2)Runloop中的自动释放池
iOS在主线程的Runloop中注册了2个Observer:
- 第一个Observer监听了"进入Runloop"事件;
- 第一个Observer监听了"即将休眠"事件和"退出Runloop"事件。
具体过程如下:
- 在"进入Runloop"时,会调用Push()
- 在"即将休眠"时,会调用Pop() 和 Push()
- 在"退出Runloop"时,会调用Pop()
Q:autorelease的释放时机?
会在Runloop即将休眠和退出Runloop时调用pop()
Q:线程中有autorelease pool吗? / 一个 thread中必须有一个autoreleasepool吗?
线程和runloop时一一对应的。不过子线程默认不开启runloop。
在Runloop中, 系统会隐式创建一个Autorelease pool;
附录:weak源码
weak 相关的数据结构
struct weak_table_t {
weak_entry_t *weak_entries; // 哈希表
size_t num_entries; // hash数组中的元素个数
uintptr_t mask;
uintptr_t max_hash_displacement; // 可能会发生的hash冲突的最大次数
};
// 哈希表中每一项的元素
struct weak_entry_t {
// 被引用
DisguisedPtr<objc_object> referent;
union {
// 动态数组
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
// 定长数组
struct {
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
根据对象获取它对应的weak表:
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
ASSERT(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
// 计算出对象的哈希值
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
// 发生哈希碰撞
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
网友评论