我们都知道OC是通过引用计数来管理对象的生命周期的.一个新创建的OC对象的默认引用计数是1,调用retain会让对象的引用计数+1,调用release会让对象的引用计数-1.当引用对象为0时,OC对象就会销毁并释放其占用的内存空间.那么这个引用计数是存放在哪里的呢?
从arm64 (5S) 架构开始,引用计数就直接存储在对象的isa指针中:
isa中存储引用计数
SideTable的结构如下:
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;//引用计数
weak_table_t weak_table;
}
其中有个散列表RefcountMap就存储着引用计数.RefcountMap中以当前对象的地址作为key,引用计数作为value.
下面我们就从runtime源码中验证一下:
步骤:NSObject.mm中搜索- (NSUInteger)retainCount:
- (NSUInteger)retainCount {
return ((id)self)->rootRetainCount();
}
进入rootRetainCount:
objc_object::rootRetainCount()
{
//如果是 TaggedPoint 类型就直接返回
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
//判断指针是否优化过: 0:普通指针 ; 1:优化过指针
if (bits.nonpointer) {
//取出 extra_rc 的值然后 加1, extra_rc存储的值是引用计数减1
uintptr_t rc = 1 + bits.extra_rc;
//判断引用计数是否过大,无法存储在 isa 中
// has_sidetable_rc 如果为1,那么引用计数就存储在 SideTable 中.
if (bits.has_sidetable_rc) {
//取出 SideTable 中的引用计数
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}
如果has_sidetable_rc为1就说明引用引用计数存储在SideTables中,我们进入sidetable_getExtraRC_nolock看看是如何从SideTables中获取引用计数的:
objc_object::sidetable_getExtraRC_nolock()
{
assert(isa.nonpointer);
//把自身 this 当做 key 取出 table
SideTable& table = SideTables()[this];
// Map 说明这是一个 散列表
//refcnts 是 RefcountMap 类型,也是一个 散列表
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
//取出 second 然后 位运算 得到 引用计数
else return it->second >> SIDE_TABLE_RC_SHIFT;
}
weak实现的原理
运行下面的代码,可以看到局部变量person再离开其作用域后就销毁了:
如果我们用一个
__strong修饰的强指针指向这个person会发现,过了person的作用域后还不会销毁:
__strong
再用weak修饰的person2指向person看看结果:
weak
发现
weak没有对person产生强引用.再用
__unsafe_unretained修饰的person3指向person:
__unsafe_unretained
会发现崩溃了,但是
person对象依然在出了自身作用域后就销毁了,说明__unsafe_unretained同样没有对person产生强引用.既然
__unsafe_unretained和weak都没有对person产生强引用,那他们有什么区别呢?区别就是:
wea不会对对象产生强引用,并且当weak指向的对象释放后,weak会把指针置为nil.防止野指针错误.而__unsafe_unretained指向的对象销毁后,__unsafe_unretained并不会把指针置为nil.所以__unsafe_unretained是不安全的,容易出现野指针访问
那么weak内部是如何实现的把指针置为nil的呢?我们还是从runtime源码中探寻答案:
步骤:在NSObject.mm中搜索- (void)dealloc:
- (void)dealloc {
_objc_rootDealloc(self);
}
进入_objc_rootDealloc:
_objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
进入rootDealloc:
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer && //如果是优化过指针
!isa.weakly_referenced && //是否被弱引用指向过
!isa.has_assoc && //是否设置过关联对象
!isa.has_cxx_dtor && //是否有c++析构函数
!isa.has_sidetable_rc))//引用计数是否存储在SideTable中
{
assert(!sidetable_present());
//直接释放对象,速度最快
free(this);
}
else {
//会走另外的流程释放,速度会慢一些
object_dispose((id)this);
}
}
进入object_dispose:
object_dispose(id obj)
{
if (!obj) return nil;
//释放前:做一些事情
objc_destructInstance(obj);
//释放
free(obj);
return nil;
}
进入objc_destructInstance:
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();//如果有c++析构函数
bool assoc = obj->hasAssociatedObjects();//如果有关联对象
// This order is important.
if (cxx) object_cxxDestruct(obj);//清除成员变量
if (assoc) _object_remove_assocations(obj);//移除关联对象
obj->clearDeallocating();//将指向当前对象的弱指针置为 nil
}
return obj;
}
进入clearDeallocating看看它内部是如何把对象置为nil的:
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
//普通isa指针
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
//优化过isa指针
clearDeallocating_slow();
}
assert(!sidetable_present());
}
进入优化过的isa指针处理方法clearDeallocating_slow:
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
//传入 this ,取出 SideTable 类型的 table
SideTable& table = SideTables()[this];
/**
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;//散列表,以当前对象地址作为key,retainCount作为value
weak_table_t weak_table;//散列表,以当前对象地址作为key,weak修饰的指向此对象的指针作为value
}
*/
table.lock();
if (isa.weakly_referenced) {
//清除 weak 指针
weak_clear_no_lock(&table.weak_table, (id)this);
}
/**
清除完weak指针后,也会把引用计数表清除
因为当前对象要销毁了
*/
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
进入weak_clear_no_lock看看是如何清除的:
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
}
weak_entry_for_referent(weak_table, referent)把弱引用表 和 当前对象地址值传进去:
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;
//利用当前对象的地址值 & 一个值 得到一个索引 begin
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;
}
}
//根据索引值得到对应的weak指针
return &weak_table->weak_entries[index];
}
最后清除:
//拿到需要清除的weak指针,清除
weak_entry_remove(weak_table, entry);
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
//释放
if (entry->out_of_line()) free(entry->referrers);
//置为nil
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
ARC帮我们做了什么?
- ARC是 LLVM 和 Runtime 的结合
- 利用LLVM自动生成 release 和 retain autorelease 代码
- 像弱引用的实现就需要 Runtime 运行时处理
autorelease
调用了autorelease的对象不需要我们手动调用release,系统会在适当的时候自动调用release去释放对象.那么系统是怎么做到这点的呢?这就牵扯到了自动释放池@autoreleasepool.我们以一下代码为实例,并且转换为c++代码:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[[Person alloc]init]autorelease];
}
return 0;
}
c++代码:
{ __AtAutoreleasePool __autoreleasepool;
//person的初始化代码
Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id,
SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)
((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init")),
sel_registerName("autorelease"));
}
我们再把person的初始化代码去掉:
{ __AtAutoreleasePool __autoreleasepool;
Person *person = [[[Person alloc]init]autorelease];
}
会发现autoreleasepool代码就被转成了上面这种样式.__AtAutoreleasePool其实就是结构体:
struct __AtAutoreleasePool {
//构造函数,生成结构体变量的时候调用
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析构函数,销毁结构体变量的时候调用
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
c++中的结构体和OC中的类很像,可以把c++的结构体看做是OC中的类,所以可以在结构体中声明函数.
也就是说执行__AtAutoreleasePool __autoreleasepool;代码的时候就会去调用__AtAutoreleasePool的构造函数:
//构造函数,生成结构体变量的时候调用
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
一旦走完autoreleasepool的大括号就会调用__AtAutoreleasePool的析构函数:
//析构函数,销毁结构体变量的时候调用
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
所以我么写的代码最后实际上的效果就是这样:
调用构造函数 返回 atautoreleasepoolobj
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc]init]autorelease];
调用析构函数,传入 atautoreleasepoolobj
objc_autoreleasePoolPop(atautoreleasepoolobj);
也就是autoreleasepool大括号的开始会调用objc_autoreleasePoolPush,大括号的结束会调用objc_autoreleasePoolPop.所以以后只要看到autoreleasepool就代表被objc_autoreleasePoolPush和objc_autoreleasePoolPop包围:
@autoreleasepool {
atautoreleasepoolobj = objc_autoreleasePoolPush();
Person *person = [[[Person alloc]init]autorelease];
@autoreleasepool {
atautoreleasepoolobj = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
所以我们只要搞清楚objc_autoreleasePoolPush()和objc_autoreleasePoolPop ()这两个函数内部做了什么,就能搞清楚autoreleasepool的本质了.
在runtime源码中搜索objc_autoreleasePoolPush和objc_autoreleasePoolPop函数会发现他们长这样:
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
发现他们都用到了AutoreleasePoolPage这个类.调用了 autorelease 的对象,最终都是通过 AutoreleasePoolPage这个类来管理的所以我们重点研究一下这个类.
AutoreleasePoolPage类的主要成员如下:
class AutoreleasePoolPage
{
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t hiwat;
{
- 一旦调用
objc_autoreleasePoolPush ()函数,就会创建一个AutoreleasePoolPage对象.这个对象占用4096个字节的内存,除了用来存放它内部的成员变量以外,剩下的空间用来存放autorelease对象的地址.比如说person对象调用了autorelease,那么person对象的地址就存在了AutoreleasePoolPage对象中. - 所有的
AutoreleasePoolPage对象通过双向链表的形式链接在一起.
比如说现在有一个AutoreleasePoolPage对象,内存地址在0x1000,那么它的结束内存地址就是0x2000.因为十六进制0x1000的十进制就是4096.person又调用了autorelease方法,那么person对象的地址就会被存放在AutoreleasePoolPage对象内部的0x1038这个位置,因为AutoreleasePoolPage对象内部有7个成员变量,每个成员变量占用8个字节,一共占用56个字节,用十六进制表示就是0x38,画图表示如下:
在
AutoreleasePoolPage对象内部有两个函数:
//返回开始存放 autorelease 对象的地址
id * begin() {
算法:自身的地址 + 自身所占空间大小: 0x1000 + 0x38
return (id *) ((uint8_t *)this+sizeof(*this));
}
//返回存放 autorelease 对象的结束地址
id * end() {
SIZE = 4096 , 自身地址 + 4096 个字节 : 0x1000 + 0x1000
return (id *) ((uint8_t *)this+SIZE);
}
如图所示:
bengin & end
如果一个
AutoreleasePoolPage对象不够存储autorelease对象,那么就会再创建AutoreleasePoolPage对象.每个AutoreleasePoolPage对象的parent指向它的上一个AutoreleasePoolPage对象,如果是第一个AutoreleasePoolPage对象,它的parent就为nil;每个AutoreleasePoolPage对象的child指向它的下一个AutoreleasePoolPage对象,如果是最后一个AutoreleasePoolPage对象,child也为nil.关系如下:
关系图
现在我们知道了objc_autoreleasePoolPush()会将autorelease对象的地址存放到AutoreleasePoolPage对象中;objc_autoreleasePoolPop会将AutoreleasePoolPage中存放的autorelease对象释放.那么他们底层是怎么存储和释放的呢?
其实一旦调用objc_autoreleasePoolPush()函数,它的内部就会将POOL_BOUNDARY入栈,并且返回其内存地址,也就是0x1038.这里的入栈并不是内存中的堆区和栈区,而是数据结构的那种栈,先进后出;POOL_BOUNDARY其实就是nil,它的底层就是个宏:(define POOL_BOUNDARY nil);也就是说AutoreleasePoolPage存放的第一个对象并不是autorelease对象person,而是POOL_BOUNDARY.其次才是一个个autorelease对象,如图:
image.png
而
objc_autoreleasePoolPop (0x1038)传入的地址就是objc_autoreleasePoolPush ()返回的地址,也就是0x1038.objc_autoreleasePoolPop拿到这个地址后会从最后一个加入到AutoreleasePoolPage的autorelease对象开始一个一个调用它们的release方法,直到POOL_BOUNDARY为止.BOUNDARY就是边界的意思,可见push函数和pop函数结合的非常巧妙.
-
AutoreleasePoolPage的next成员变量永远指向下一个能存放autorelease对象的地址. - 使用
_objc_autoreleasePoolPrint私有函数查看autoreleasepool自动释放池的情况:
//使用 extern 关键字声明这个函数,即使这个函数在 Foundation 框架内部
//编译器会自动去查找这个方法并调用,这是c语言语法
extern void _objc_autoreleasePoolPrint(void);
我们使用一下:
person autorelease之前
person autorelease之后
多个autoreleasepool
超出一个 page 容量
先面我们将从源码上验证上诉结论:
push源码分析:
// 第一步:
static inline void *push()
{
id *dest;
if (DebugPoolAllocation) {
// Each autorelease pool starts on a new pool page.
//如果没有 page 就创建一个 page,把 POOL_BOUNDARY 传进去
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
//有page
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
//第二步
id *autoreleaseNewPage(id obj)
{
//调用hotPage,创建一个page
AutoreleasePoolPage *page = hotPage();
//判断 page 是不是满了
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}
//第三步
id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
{
// The hot page is full.
// Step to the next non-full page, adding a new page if necessary.
// Then add the object to that page.
assert(page == hotPage());
assert(page->full() || DebugPoolAllocation);
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
//往 page 中添加POOL_BOUNDARY
return page->add(obj);
}
autorelease源码分析:
//第一步:
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
//将调用了 autorelease 的对象传进去
return AutoreleasePoolPage::autorelease((id)this);
}
//第二步:
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
//进入 autoreleaseFast
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
//第三步:
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
//把调用了 autorelease 的对象 加入到 page
return page->add(obj);
} else if (page) {
//如果有page,并且 page 满了
return autoreleaseFullPage(obj, page);
} else {
//如果没有 page,就创建 page
return autoreleaseNoPage(obj);
}
}
pop源码分析:
//第一步:
static inline void pop(void *token) {
//token 就是 POOL_BOUNDARY
page = pageForPointer(token);
stop = (id *)token;
//释放对象,直到遇到 POOL_BOUNDARY
page->releaseUntil(stop);
}
//第二步:
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
while (this->next != stop) {
//如果 不是 POOL_BOUNDARY 就一直释放对象
if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}
autorelease 对象什么时候释放?
我们知道调用了autorelease的对象会在适当的时候由系统去调用release释放,但是这个适当的时候是什么时候呢?思考一下以下代码的person对象什么时候会被释放:
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[[Person alloc]init]autorelease];
NSLog(@"viewDidLoad");
}
- (void)viewWillAppear:(BOOL)animated{
NSLog(@"viewWillAppear");
}
- (void)viewDidAppear:(BOOL)animated{
NSLog(@"viewDidAppear");
}
有人可能会觉得是走完viewDidLoad的大括号释放的,我们运行一下代码看看结果:
AutoRelease对象和runloop[18097:3714897] viewDidLoad
AutoRelease对象和runloop[18097:3714897] viewWillAppear
AutoRelease对象和runloop[18097:3714897] -[Person dealloc]
AutoRelease对象和runloop[18097:3714897] viewDidAppear
事实上是走完viewDidLoad和viewWillAppear才释放的,为什么会这样呢?其实这和runloop有关系.
iOS在主线程中注册了两个Observer用来处理自动释放池相关的工作.Observer是用来监听runloop的状态的,比如进入runloop,runloop即将处理timer,runloop即将休眠等等.
我们打印一下NSLog(@"%@",[NSRunLoop currentRunLoop]);看看:
// 监听状态为 1
"<CFRunLoopObserver 0x6000021b0960 [0x7fff80615350]>{valid = Yes, activities =
0x1, repeats = Yes, order = -2147483647, callout =
_wrapRunLoopWithAutoreleasePoolHandler (0x7fff47848c8c), context = <CFArray
0x600001ec6610 [0x7fff80615350]>{type = mutable-small, count = 1, values = (\n\t0 :
<0x7fdbda801038>\n)}}",
//监听状态为160 相当于 kCFRunLoopBeforeWaiting | kCFRunLoopExit
"<CFRunLoopObserver 0x6000021b0a00 [0x7fff80615350]>{valid = Yes, activities =
0xa0, repeats = Yes, order = 2147483647, callout =
_wrapRunLoopWithAutoreleasePoolHandler (0x7fff47848c8c), context = <CFArray
0x600001ec6610 [0x7fff80615350]>{type = mutable-small, count = 1, values = (\n\t0 :
<0x7fdbda801038>\n)}}"
会发现有两个Observer会调用_wrapRunLoopWithAutoreleasePoolHandler函数,并且这两个Observer分别监听了两个状态:
-
activities = 0x1: 0x1 = 1 -
activities = 0xa0=xa0 = 160
我们再来看看runloop中有哪些状态:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), 1
kCFRunLoopBeforeTimers = (1UL << 1), 4
kCFRunLoopBeforeSources = (1UL << 2), 8
kCFRunLoopBeforeWaiting = (1UL << 5), 32
kCFRunLoopAfterWaiting = (1UL << 6), 64
kCFRunLoopExit = (1UL << 7), 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
对照runloop的状态我们知道:
-
activities = 0x1: 就是监听kCFRunLoopEntry进入runloop. -
activities = 0xa0就是监听kCFRunLoopBeforeWaiting | kCFRunLoopExit即将休眠和退出.
那么这两个Observer做了什么事情呢?
- 第一个
Observer监听kCFRunLoopEntry会调用objc_autoreleasePoolPush (). - 第二个
Observer:
监听kCFRunLoopBeforeWaiting时间会调用objc_autoreleasePoolPop (),然后再调用objc_autoreleasePoolPush ();
监听kCFRunLoopExit事件会调用objc_autoreleasePoolPop ()
也就是说person对象什么时候调用release,是由runloop来控制的,它可能是在某次runloop循环中,runloop休眠之前调用了release;并且从上面的打印结果可以分析出来,viewDidLoad和viewWillAppear是在同一次runloop循环中.
ARC环境下,方法中局部变量出了方法会立即释放吗?
我们可以试一下:
局部对象的释放时机
从打印结果可以看到,
person对象在viewDidLoad之后,viewWillAppear之前就释放了.说明了ARC自动生成的并不是autorelease,而是在viewDidLoad大括号之前生成了person release.所以我们应该分两种情况分析:
- 如果ARC自动生成的是
autorelease,那么局部对象是在runloop某次循环即将休眠的时候释放. - 如果ARC自动生成的是
release,那么局部对象就会出了方法就释放.












网友评论