苹果内存管理方案主要为MRC
和ARC
TaggedPointer
:小对象类型,NSDate、NSNumber等
NonpointerIsa
:非指针型isa
散列表
:引用计数表,弱引用表
TaggedPointer
上述代码中,两个name
的类型其实是不一样的。
name
是NSTaggedPointerString
,他是经过Xcode
优化的,存放在常量区。
这里的
name
是NSCFString
,存放在堆区。
接下来看一下源码,看看这两个类型有什么区别。
我们知道setter
方法,在底层会走reallySetProperty
- 查看
objc_retain
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
当他是TaggedPointer
类型的时候,不走retain
直接返回。
- 查看
objc_release
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
同样的,TaggedPointer
类型也不走release
。不需要进行内存管理,在常量区,由系统释放。小对象类型读取比一般对象快了3倍,创建快了100倍。
在read_images中有一个处理TaggedPointer
的方法
- 点击查看
initializeTaggedPointerObfuscator
static void
initializeTaggedPointerObfuscator(void)
{
if (sdkIsOlderThan(10_14, 12_0, 12_0, 5_0, 3_0) ||
// Set the obfuscator to zero for apps linked against older SDKs,
// in case they're relying on the tagged pointer representation.
DisableTaggedPointerObfuscation) {
objc_debug_taggedpointer_obfuscator = 0;
} else {
// 在iOS 10.14之后,objc_debug_taggedpointer_obfuscator与上~_OBJC_TAG_MASK,做了一次处理
arc4random_buf(&objc_debug_taggedpointer_obfuscator,
sizeof(objc_debug_taggedpointer_obfuscator));
objc_debug_taggedpointer_obfuscator &= ~_OBJC_TAG_MASK;
}
}
-
搜索
objc_debug_taggedpointer_obfuscator
,可以找到以下代码
taggedpointer
在编码和解码的时候,指针ptr
要做一次与objc_debug_taggedpointer_obfuscator
的异或操作 -
验证
将self.name
的原地址与objc_debug_taggedpointer_obfuscator
异或得到真正的地址,而且地址中的61
,正是a
的ASCII
值
这不仅是一个简单的地址,还包含了值。
- 查看
_objc_isTaggedPointer
# define _OBJC_TAG_MASK (1UL<<63)
static inline bool
_objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
在64位中,取最高位,如果有值就是TaggedPointer
对象。
- 查看
objc_tag_index_t
这是TaggedPointer
的flag
类型,2
表示NSString
,3
表示NSNumber
,4
表示NSIndexPath
MRC & ARC
retain
从objc_retain
开始
id
objc_retain(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
- 查看
objc_object::retain()
inline id
objc_object::retain()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
return rootRetain();
}
return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(retain));
}
会走rootRetain
方法。
- 查看
rootRetain
ALWAYS_INLINE id
objc_object::rootRetain()
{
return rootRetain(false, false);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;
bool sideTableLocked = false;
bool transcribeToSideTable = false;
isa_t oldisa;
isa_t newisa;
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//判断是否为nonpointer isa,不是nonpointer_isa操作散列表rentain
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return (id)this;
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
//判断是否正在析构,没有必要操作引用计数
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;//引用计数+1
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//carry 是一个标识位,代表你的引用计数位用完了,这时候要借助散列表存储引用计数
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
//引用计数满了之后,把引用计数的一半存在extra_rc中
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));
if (slowpath(transcribeToSideTable)) {
// 引用计数的另一半存到散列表中
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
release
- 查看
objc_release
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
TaggedPointer
对象直接返回
- 查看
release()
inline void
objc_object::release()
{
ASSERT(!isTaggedPointer());
if (fastpath(!ISA()->hasCustomRR())) {
rootRelease();
return;
}
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(release));
}
调用rootRelease
- 查看
rootRelease
ALWAYS_INLINE bool
objc_object::rootRelease()
{
return rootRelease(true, false);
}
ALWAYS_INLINE bool
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;
bool sideTableLocked = false;
isa_t oldisa;
isa_t newisa;
retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//非nonpointer_isa直接处理散列表
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (rawISA()->isMetaClass()) return false;
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
uintptr_t carry;
//引用计数减1 这里的carry,是标志着isa里的 extra_rc不够减,需要去散列表处理
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));
if (slowpath(sideTableLocked)) sidetable_unlock();
return false;
underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
// abandon newisa to undo the decrement
newisa = oldisa;
if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}
// Transfer retain count from side table to inline storage.
if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}
// 从散列表中拿出满值的一半
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);
if (borrowed > 0) {
// 满值的一半减1 赋值给isa的extra_rc
newisa.extra_rc = borrowed - 1;
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}
if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}
// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}
//dealloc函数
if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
if (slowpath(sideTableLocked)) sidetable_unlock();
__c11_atomic_thread_fence(__ATOMIC_ACQUIRE);
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, @selector(dealloc));
}
return true;
}
retainCount
查看retainCount
源码
inline uintptr_t
objc_object::rootRetainCount()
{
if (isTaggedPointer()) return (uintptr_t)this;
sidetable_lock();
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);
if (bits.nonpointer) {
uintptr_t rc = 1 + bits.extra_rc;//引用计数加1
if (bits.has_sidetable_rc) {
rc += sidetable_getExtraRC_nolock();//加上散列表里的引用计数
}
sidetable_unlock();
return rc;
}
sidetable_unlock();
return sidetable_retainCount();//非nonpointer 直接返回散列表里的引用计数
}
当为nonpointer时,返回的引用计数额外加1。
非nonpointer时,返回散列表里的引用计数。
dealloc
dealloc
在前面已经分析过了传送们
散列表
查看SideTable
源码
struct SideTable {
spinlock_t slock;//操作散列表时要开解锁
RefcountMap refcnts;//引用计数表
weak_table_t weak_table;//弱引用表
SideTable() {
memset(&weak_table, 0, sizeof(weak_table));
}
~SideTable() {
_objc_fatal("Do not delete SideTable.");
}
void lock() { slock.lock(); }
void unlock() { slock.unlock(); }
void forceReset() { slock.forceReset(); }
// Address-ordered lock discipline for a pair of side tables.
template<HaveOld, HaveNew>
static void lockTwo(SideTable *lock1, SideTable *lock2);
template<HaveOld, HaveNew>
static void unlockTwo(SideTable *lock1, SideTable *lock2);
};
- 查看
SideTablesMap
真机里
StripeCount
为8,最多有8张表
NSTimer使用问题
1. NSTimer要加入runLoop才会执行
上图代码,timeRun
方法并不会执行,创建NSTimer
时使用timerWith
方法,需要将timer
加入到runLoop
;或者使用scheduledTimer
创建NSTimer
//timerWithTimeInterval创建的需要加入到NSRunLoop
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeRun) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
//scheduledTimerWithTimeInterval创建
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeRun) userInfo:nil repeats:YES];
2. weakSelf
接着在控制台上打印
可以看到
self
和weakSelf
指针指向同一块地址,而他们本身的地址不同。
3. NSTimer的强引用
页面退出后,controller
没有走dealloc
方法,self.timer
没有被释放
处理方法:
- 在
didMoveToParentViewController
释放timer
- (void)didMoveToParentViewController:(UIViewController *)parent {
[self.timer invalidate];
self.timer = nil;
}
- 使用
NSTimer
的block
形式
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
static int i = 0;
i++;
NSLog(@"%d",i);
}];
可是这里出现了新的问题,虽然VC
释放了,但是timer
还在打印
下面将探索这两个问题的由来。
- 为什么
VC
释放不掉?timer
释放不掉? - 使用
block
的形式,为什么VC
可以释放了?
NSTimer
在foundation
库中,未开源。这里我们只能查看官方文档,command + shift + 0
搜索timerWithTimeInterval
timer
会对target
保持一个强引用,知道timer
释放。
既然是这里的强引用引发的问题,那我们使用__weak
是不是就能解决问题呢?
//timerWithTimeInterval创建的需要加入到NSRunLoop
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1 target:weakSelf selector:@selector(timeRun) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
查看结果:
打印正常,还是没有走
dealloc
方法。__weak
不能解决
[NSRunLoop currentRunLoop]
强持有 -> timer
self -> block -> weakSelf
self -> timer -> weakSelf -> self
这两个模型是不一样的,block
捕捉的是指针地址,timer
捕捉的是内存。
这是
block
的源码,拿到的是指针地址。如果传入的是weakSelf
,这里拿到的就是weakSelf
的指针地址,与原本的self
已经没有关系了。而timer
是直接强持有<SecondViewController: 0x7fc048412da0>
这片内存,故__weak
不起作用。
既然原因我们知道了,那么如何解决这个问题呢?
解决思路:打破这一层强持有
思路一:VC
的dealloc
不能来,那么我们手动销毁timer
,在合适的地方把timer
销毁了,timer
销毁了,对VC
的强持有就没有了,能够调用dealloc
上述在didMoveToParentViewController
销毁timer
就是这样的例子。
思路二:中介者模式。不让timer
强持有self
,给一个中介者
//中介者模式
self.target = [NSObject alloc];
class_addMethod([NSObject class], @selector(timeRun), (IMP)timeRun, "v@:");
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.target selector:@selector(timeRun) userInfo:nil repeats:YES];
思路三:虚基类Proxy
- (void)viewDidLoad {
[super viewDidLoad];
self.proxy = [LYProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(timeRun) userInfo:nil repeats:YES];
}
@interface LYProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface LYProxy()
@property (nonatomic, weak) id object;
@end
@implementation LYProxy
+ (instancetype)proxyWithTransformObject:(id)object{
LYProxy *proxy = [LYProxy alloc];
proxy.object = object;
return proxy;
}
// 仅仅添加了weak类型的属性还不够,为了保证中间件能够响应外部self的事件,需要通过消息转发机制,让实际的响应target还是外部self,这一步至关重要,主要涉及到runtime的消息机制。
// 转移
// 强引用 -> 消息转发
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
这样就不会强引用VC
,在VC
释放的时候,销毁timer
,即可释放proxy
。
4. @autoreleasepool自动释放池
首先思考这几个问题:
- 临时变量什么时候释放?
- 自动释放池原理?
- 自动释放池能否嵌套使用?
在libobjc
源码中,main
方法就有一个autoreleasepool
。
使用
clang
编译成.cpp
文件可以看到多了一行__AtAutoreleasePool
代码。
-
全局搜索
__AtAutoreleasePool
可以看到他是一个结构体,里面有一个atautoreleasepoolobj
变量,还有构造方法和析构方法。
这也就意味着,autoreleasepool
创建的时候会调用objc_autoreleasePoolPush()
,析构的时候会调用objc_autoreleasePoolPop
-
开启汇编,验证
-
按住
control + step into
可以看到源码在libobjc.A.dylib
库中,接下来去libobjc.A.dylib
查看源码就可以了。 -
在
libobjc
源码中搜索Autorelease Pool
- 线程的自动释放池,是一堆指针。指针都指向将要被释放的对象,或者是池子的边界。
- pop的时候,所有对象都会释放。
- 堆栈被分为一个双向链接的页面列表。
- 线程本地存储,也可以保存自动释放对象。
下面开始验证环节:
- 搜索
objc_autoreleasePoolPush
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
-
点击进入
AutoreleasePoolPage
AutoreleasePoolPage
是一个类,继承于AutoreleasePoolPageData
-
查看
AutoreleasePoolPageData
其中有一个parent
和child
,这也就验证了这是一个双向链表。
magic:
用来校验AutoreleasePoolPage的结构是否完整
next:
指向最新添加的autoreleased
对象的下一个位置,初始化时指向begin()
thread:
指向当前线程
parent:
父节点,第一个节点的parent
值为nil
child:
子节点,最后一个节点的child
值为nil
depth:
深度,从0开始,往后递增1
hiwat:
代表high water mark
最大入栈数量标记
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
当前地址 + 自身属性的内存大小
- 返回查看
push()
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
autoreleaseNewPage
和autoreleaseFast
其实差不多,都是取到hotpage
,把POOL_BOUNDARY
哨兵加进去。
到这里,AutoreleasePoolPage
准备工作就做完了,下面看一下如何把对象加入其中。
- 借助
_objc_autoreleasePoolPrint
打印释放池情况
关闭ARC
,排除ARC
的影响
打印结果如下
上面的两个是哨兵对象,由于我这里是嵌套了
@autoreleasepool
,所以有两个哨兵对象。下面五个是NSObject
对象。
下面我们看一下page
的上限在哪?把循环次数改为1000
打印结果如下:
继续往下翻,可以找到这样的结构。
这里重新生成了一张hot page
,地址后面从0
开始。
这里可以计算一下,一张表可以存储505
个对象,这一页的大小是4096
字节,也就是4kb
这里还需要注意一点,在新的一页中,并没有新增哨兵对象。
- 查看
AutoreleasePoolPage
结构体
size
在i386
中确实为4096
字节
接下来查看- autorelease
方法,
-
汇编查看
-
下符号断点
objc_autorelease
-
运行
可以看出确实是调用了objc_autorelease
方法 搜索
objc_autorelease(
id
objc_autorelease(id obj)
{
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
TaggedPointer
对象直接return
- 查看
autorelease
这里走的是rootAutorelease
方法
- 查看
rootAutorelease
走rootAutorelease2
方法
- 查看
rootAutorelease2
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
ASSERT(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
实际上走的是AutoreleasePoolPage
的autorelease()
方法
- 查看
autorelease(id obj)
static inline id autorelease(id obj)
{
ASSERT(obj);
ASSERT(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
这里就到了autoreleaseFast
方法,与上面加入哨兵是同一个方法。
- 查看
autoreleaseFast
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
- 判断
页存在
并且没满
直接添加obj - 判断
页存在
并且满了
创建新页,添加obj - 页不存时,创建页添加obj
- 查看
add(id obj)
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
这里可以很明显的看出next
指向了最新加入的obj
后面的位置,也就是下一个obj
地址
- 查看
autoreleaseFullPage
static __attribute__((noinline))
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);
return page->add(obj);
}
- 不断遍历子节点,查找一个没有满的
page
,如果没有则新建一个page
,上一个page
的child
指向new page
- 把
page
设置为hot
,加入obj
接下来查看pop
方法,看page
中的数据如何出来。
- 搜索
objc_autoreleasePoolPop
NEVER_INLINE
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
- 点击查看
pop
方法
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;//对空页面的处理
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
page = hotPage();
if (!page) {
// Pool was never used. Clear the placeholder.
return setHotPage(nil);
}
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
page = coldPage();
token = page->begin();
} else {
page = pageForPointer(token);
}
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {//不是边界
if (stop == page->begin() && !page->parent) {//等于begin() 没有父节点 不用处理
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);//
}//这是一个容错处理
}
if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
return popPageDebug(token, page, stop);
}
return popPage<false>(token, page, stop);
}
真正要研究的是最下面的popPage
方法
- 查看
popPage
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//遍历page中的所有 obj,release obj
page->releaseUntil(stop);
// memory: delete empty children 销毁所有的空页面
if (allowDebug && DebugPoolAllocation && page->empty()) {
//page 为空销毁自身,设置父节点为hot page
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
//没有父节点,销毁当前page
page->kill();
setHotPage(nil);
} else if (page->child) {
//page 容量少于一半 child销毁
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {//page的子节点 还有子节点 就销毁
page->child->child->kill();
}
}
}
在这个方法中销毁空page
- 查看
releaseUntil
void releaseUntil(id *stop)
{
//一直循环,stop为哨兵对象的值 一直pop和release obj 直到哨兵对象为止
while (this->next != stop) {
AutoreleasePoolPage *page = hotPage();
//如果page为空,指向他的parent
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
id obj = *--page->next;//obj 赋值为page中的最后一个元素
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));//清理page->next
page->protect();
if (obj != POOL_BOUNDARY) {
objc_release(obj);//obj release
}
}
setHotPage(this);
#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
ASSERT(page->empty());
}
#endif
}
在这个方法中pop
和release obj
对象