内存布局
介绍
先上一内存布局图
解释
- 内核区:大概有占有1个GB,由系统控制处理:比如我们日常触摸屏幕产生的事件其实就是这部分处理的,处理完后通过端口通讯下发给上层的框架
- 栈区 :函数、方法、局部变量等会储存在这里面
- 堆区 :通过alloc分配对象、block copy...
- bbs区:未初始化的全局变量、静态变量...
- data区:已初始化的全局变量、静态变量...
- text: 程序代码
- 保留区:由系统控制处理
其他
- (0xC0000000 = 3221225472 = 3GB),所以从栈区到保留区一共占有3GB
- 一般我们只讨论栈区-text区这5大区域
- 栈区向下增长,内存占有小,处理里速度快;堆区向上增长,内存占有大,处理里速度慢
- bbs和data区在不区分是否初始化时,一般统称全局区
地址开头
- 栈区内存地址:一般以 0x7开头
- 堆区内存地址:一般以 0x6开头
- data区、bbs区内存地址:一般以 0x1开头
面试题
- 1、全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
答:有。全局变量存在全局区(bbs区/data区),局部变量存在栈区
- 2、下面Block 是否可以直接修改全局变量?
static NSString *K_Name = @"lala";
- (void)test {
[UIView animateWithDuration:1 animations:^{
K_Name = @"dingding";
}];
}
答:可以,全局变量可以全局访问
- 3、下面打印是怎样的?
##### Person
//注意personNum是定义在Person.h文件中的静态变量
static int personNum = 100;
@interface Person : NSObject
- (void)run;
+ (void)eat;
@end
@implementation Person
- (void)run{
personNum ++;
NSLog(@"Person内部:%@-%p--%d",self,&personNum,personNum);
}
+ (void)eat{
personNum ++;
NSLog(@"Person内部:%@-%p--%d",self,&personNum,personNum);
}
@end
#### 分类 Person (ca)
#import "Person.h"
@interface Person (ca)
- (void)cate_method;
@end
@implementation Person (ca)
- (void)cate_method{
NSLog(@"Person内部:%@-%p--%d",self,&personNum,personNum);
}
@end
题目
#### ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"vc:%p--%d",&personNum,personNum); // 100
personNum = 10000;
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[Person new] run]; // 100 + 1 = 101
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[Person eat]; // 102
NSLog(@"vc:%p--%d",&personNum,personNum); // 10000
[[Person alloc] cate_method];
}
打印:
vc:0x102730484--100
vc:0x102730484--10000
Person内部:-0x102730460--101
vc:0x102730484--10000
Person内部:LGPerson-0x102730460--102
vc:0x102730484--10000
Person内部:-0x102730488--100
答:首先static变量时可以边修改的;static变量的作用域与对象、类、分类没关系,只与文件有关系;
内存管理方案
apple在内存管理方面提供了三种方案(TaggetPointer、NONPOINTER_ISA、散列表),严谨的说应该是三种方案共同管理内存。
TaggetPointer
介绍
- 1、
Tagged Pointer是专⻔⽤来存储⼩的对象,例如NSNumber,NSDate、、、 - 2、
Tagged Pointer指针的值不再是地址了,⽽是真正的值。所以,实际上它不再是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。所以,它的内存并不存储 在堆中,也不需要malloc和`free`` - 3、在内存读取上有着3倍的效率,创建时⽐以前快106倍。
原理
首先理解一个操作:对同一个数值^(异或)操作两次,得到的还是原来的数值。eg:
1000 0001
^ 0001 1000
------------
1001 1001
1001 1001
^ 0001 1000
------------
1000 0001
下面是TaggedPointer对象的源码,可以看到,也是对同一个数值objc_debug_taggedpointer_obfuscator进行^异或操作
// 存值
static inline void * _Nonnull
_objc_makeTaggedPointer(objc_tag_index_t tag, uintptr_t value) {
//objc_tag_index_t 是一个枚举值 用于标定不同tag类型,根据不同的类型的tag值进行不同的左右移和MASK操作
if (tag <= OBJC_TAG_Last60BitPayload) {
//将类型tag和值Value进行打包(左右移和MASK操作),得到一个result
uintptr_t result =
(_OBJC_TAG_MASK |
((uintptr_t)tag << _OBJC_TAG_INDEX_SHIFT) |
((value << _OBJC_TAG_PAYLOAD_RSHIFT) >> _OBJC_TAG_PAYLOAD_LSHIFT));
// 用 result进行encode ^ 异或操作,返回一个指针
return _objc_encodeTaggedPointer(result);
} else {//下面操作一样
uintptr_t result =
(_OBJC_TAG_EXT_MASK |
((uintptr_t)(tag - OBJC_TAG_First52BitPayload) << _OBJC_TAG_EXT_INDEX_SHIFT) |
((value << _OBJC_TAG_EXT_PAYLOAD_RSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_LSHIFT));
return _objc_encodeTaggedPointer(result);
}
}
//取值
static inline uintptr_t
_objc_getTaggedPointerValue(const void * _Nullable ptr) {
//将指针ptr先decode ^ 操作取出value(就是上面的result)
uintptr_t value = _objc_decodeTaggedPointer(ptr);
//value解包(左右移和MASK操作)得到value
uintptr_t basicTag = (value >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
if (basicTag == _OBJC_TAG_INDEX_MASK) {
return (value << _OBJC_TAG_EXT_PAYLOAD_LSHIFT) >> _OBJC_TAG_EXT_PAYLOAD_RSHIFT;
} else {
return (value << _OBJC_TAG_PAYLOAD_LSHIFT) >> _OBJC_TAG_PAYLOAD_RSHIFT;
}
}
extern uintptr_t objc_debug_taggedpointer_obfuscator;
//encode ^ 操作
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr) {
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}
//decode ^ 操作
static inline uintptr_t
_objc_decodeTaggedPointer(const void * _Nullable ptr) {
return (uintptr_t)ptr ^ objc_debug_taggedpointer_obfuscator;
}
//objc_tag_index_t 是一个枚举值 用于标定不同tag类型
enum objc_tag_index_t : uint16_t {
// 60-bit payloads
OBJC_TAG_NSAtom = 0,
OBJC_TAG_1 = 1,
OBJC_TAG_NSString = 2, //NSString类的tag为2
OBJC_TAG_NSNumber = 3, //NSNumber类的tag为2
OBJC_TAG_NSIndexPath = 4,
OBJC_TAG_NSManagedObjectID = 5,
OBJC_TAG_NSDate = 6,
// 60-bit reserved
OBJC_TAG_RESERVED_7 = 7,
// 52-bit payloads
OBJC_TAG_Photos_1 = 8,
OBJC_TAG_Photos_2 = 9,
OBJC_TAG_Photos_3 = 10,
OBJC_TAG_Photos_4 = 11,
OBJC_TAG_XPC_1 = 12,
OBJC_TAG_XPC_2 = 13,
OBJC_TAG_XPC_3 = 14,
OBJC_TAG_XPC_4 = 15,
OBJC_TAG_NSColor = 16,
OBJC_TAG_UIColor = 17,
OBJC_TAG_CGColor = 18,
OBJC_TAG_NSIndexSet = 19,
OBJC_TAG_First60BitPayload = 0,
OBJC_TAG_Last60BitPayload = 6,
OBJC_TAG_First52BitPayload = 8,
OBJC_TAG_Last52BitPayload = 263,
OBJC_TAG_RESERVED_264 = 264
};
源码结论
- 存值:当一个
TaggedPointer对象在存值时,先将对象类型tag和值value打包(左右移和MASK操作)处理得到一个result,然后result和一个随机的常量值objc_debug_taggedpointer_obfuscator进行^异或操作返回一个包装过的TaggedPointer指针。 - 取值 :取值的时候刚好相反,先用
TaggedPointer指针和一个随机的常量值objc_debug_taggedpointer_obfuscator进行^异或操作返回result,然后result解包得到值value。
面试题
- 为什么两个方法一个会崩溃,另一个不会崩溃呢?
//MARK: - taggedPointer 面试题
@property (nonatomic, strong) NSString *nameStr;
- (void)taggedPointer_NOCrash {
dispatch_queue_t queue = dispatch_queue_create("com.fun.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
self.nameStr = [NSString stringWithFormat:@"fun"];
NSLog(@"%@",self.nameStr);
});
}
}
- (void)taggedPointer_Crash {
dispatch_queue_t queue = dispatch_queue_create("com.fun1.cn", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10000; i++) {
dispatch_async(queue, ^{
self.nameStr = [NSString stringWithFormat:@"fun 好好学习.天天向上。发展体育运动,增强国民体质"];
NSLog(@"%@",self.nameStr);
});
}
}
答:首先 这两句代码self.nameStr = [NSString stringWithFormat:@"fun 好好学习.天天向上。发展体育运动,增强国民体质"]; NSLog(@"%@",self.nameStr);代表着get/set方法;对象的get/set方法内部操作就是先新值 retian,后旧值 release;让我们看下retian/release源码实现;
void
objc_storeStrong(id *location, id obj) {
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
__attribute__((aligned(16), flatten, noinline))
id
objc_retain(id obj) {
if (!obj) return obj;
if (obj->isTaggedPointer()) return obj;
return obj->retain();
}
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj) {
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}
很明显正常对象的get/set方法内部操作就是先新值 retian,后旧值 release,在多线程访问的情况下,新值 retian,旧值 release调用错乱,导致野指针或者空,所以崩溃;
但是如果是对象是taggedPointer对象时,对象不会进行retian/release操作,所以不会崩溃;而self.nameStr = [NSString stringWithFormat:@"fun"];nameStr是 NSTaggedPointerString类型;self.nameStr = [NSString stringWithFormat:@"fun 好好学习.天天向上。发展体育运动,增强国民体质"];nameStr是——NSCFString类型。
NONPOINTER_ISA
介绍
在最新的objc2源码中可知万物皆objc_object对象,在objc_object对象内部有一个isa属性;这个isa有可能是纯指针,也有可能除包含指针外还包含其他信息,例如对象的引用计数、是否被弱引用...这时这个isa就是NONPOINTER_ISA。isa是isa_t类型的联合体,其内部通过位域技术储存很多了对象的信息。
isa_t源码
源码中有注释就不再赘述了
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
// __arm64__ defined in isa.h
//这里把 ISA_BITFIELD 内部的宏直接写进来了 以arm64构架说明
/*表示是否对isa开启指针优化 。0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等。*/
uintptr_t nonpointer : 1;
/*关联对象标志位*/
uintptr_t has_assoc : 1;
/*该对象是否有C++或Objc的析构器,如果有析构函数,则需要做一些析构的逻辑处理,如果没有,则可以更快的释放对象*/
uintptr_t has_cxx_dtor : 1;
/*存在类指针的值,开启指针优化的情况下,arm64位中有33位来存储类的指针*/
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/
/*判断当前对象是真的对象还是一段没有初始化的空间*/
uintptr_t magic : 6;
/*是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快*/
uintptr_t weakly_referenced : 1;
/*是否正在释放*/
uintptr_t deallocating : 1;
/*是否有辅助的引用计数散列表*/
uintptr_t has_sidetable_rc : 1;
/*表示该对象的引用计数值,满了就会存在sidetable 中*/
uintptr_t extra_rc : 19;
};
#endif
};
源码中的extra_rc就是用来存储引用计数的,具体原理放到下面的引用计数部分说明。
散列表--引用计数&弱引用计数
介绍
系统维护了一张全局的Hash表,里面存了一张张SideTable散列表,而这个散列表中就储存了对象的引用计数以及弱引用情况。
SideTable内部结构
SideTable 内部结构
struct SideTable {
spinlock_t slock;//锁,用于控制数据访问安全
RefcountMap refcnts;//引用计数表s
weak_table_t weak_table;//弱引用计数表s
...
...
};
结构解释
- 系统维护了一张全局
Hash表,用于管理对象的引用计数和弱引用情况。内部就是一个个散列表SideTble,使用对象的内存地址作为SideTable的Key(地址经过哈希计算)。并且不同的对象可能在同一张散列表SideTble中。 spinlock_t slock:锁,用于控制这张散列表SideTble的数据访问安全。RefcountMap refcnts:引用计数表RefcountMap,用于储存对象的引用计数情况,下面会具体说明weak_table_t weak_table:弱引用情况表,用于储存对象的弱引用情况,下面会具体说明
RefcountMap 引用计数表
在isa_t中extra_rc位引用计数满了后会把一半的引用计数放到某个散列表SideTable中的引用计数表中;具体操作如下:
RefcountMap表内部是一个个BucketT(桶)连起来的Buckets(类似于数组),- 又通过对象内存的地址经过运算(这部分很复杂,我也说不太明白)得到具体
BucketT(桶) BucketT(桶)里面封装了对象的引用计数。之后就是引用计数的+-了
weak_table_t 弱引用计数表
- 1、
weak_table_t是个二维数组,里面包含了一个个weak_table,weak_table里面是一个个weak_entry数组 - 2、当一个对象的属性被设置成
weak时,weak_table表中会查找当内部有没有该对象的弱引用数组(weak_entry数组),如果有就直接插入这个属性到这个weak_entry数组,没有就先创建weak_entry数组再插入 - 3、当对象被释放时
delloc,会通过对象指针去查找weak_table没有该对象的weak_entry数组,有的话遍历weak_entry数组,将内部的属性置为nil;最后将这个weak_entry数组remove
为什么是 weak_entry数组
因为一个对象可能拥有多个弱应用属性
举例
我们把系统维护的Hash表当成是一个男生宿舍楼
对象:学生
对象的内存地址:学生的姓名
hash表:一栋宿舍楼
SideTable:寝室
spinlock_t:寝室门上的锁,管理学生出入
RefcountMap:某一个寝室
引用计数:寝室某个学生拥有的书本数
weak_table_t:另一个寝室
weak_table:学生s
弱引用情况就是学生拥有的女朋友数量的情况
所以,不同的对象(学生)可能在同一个SideTable(寝室)中
-不同点:引用计数类似于一维数组,而弱引用情况是一个二维数组
引用计数
在上面内存管理方案中已经介绍了TaggetPointer、NONPOINTER_ISA、散列表。下面说下引用计数具体如何工作的。引用计数的核心就是对象的retain、release方法
retain
直接看源码,内有详细的解释,就不再赘述
-(id) retain {
return _objc_rootRetain(self);
}
NEVER_INLINE id
_objc_rootRetain(id obj) {
ASSERT(obj);
return obj->rootRetain();
}
ALWAYS_INLINE id
objc_object::rootRetain() {
return rootRetain(false, false);
}
ALWAYS_INLINE id
objc_object::rootRetain(bool tryRetain, bool handleOverflow) {
//判断是否是TaggedPointer,是的话直接返回
if (isTaggedPointer()) return (id)this;
//用于记录锁状态
bool sideTableLocked = false;
bool transcribeToSideTable = false;
//初始化isa_t 用于后面赋值
isa_t oldisa;
isa_t newisa;
// 真正 retain 引用计数处理
do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//是否用到了sidetable辅助处理引用计数
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;
//sidetable内部处理引用计数
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
//判断对象是否正在dealloc
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
//其实就是对isa的extra_rc变量进行+1,前面说到isa会存很多东西
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
//判断是否需要引用计数迁移
if (slowpath(carry)) {
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
//留一半的引用计数
//准备复制另一半引用计数到sideTable
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)));
//是否将引用计数迁移到sidetable中
if (slowpath(transcribeToSideTable)) {
// Copy the other half of the retain counts to the side table.
//从newisa.extra_rc复制一半的引用计数到sidetable中
sidetable_addExtraRC_nolock(RC_HALF);
}
if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}
id
objc_object::sidetable_retain() {
#if SUPPORT_NONPOINTER_ISA
ASSERT(!isa.nonpointer);
#endif
//通过this内存地址拿到对应的SideTable
SideTable& table = SideTables()[this];
//加锁
table.lock();
//又通过this内存地址拿到对象的refcntStorage(内存存有引用计数)
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
//引用计数 +1
refcntStorage += SIDE_TABLE_RC_ONE;
}
//解锁
table.unlock();
//返回对象
return (id)this;
}
源码简介
1.判断是不是TaggedPointer对象,如果是直接返回,不做引用计数处理
2.如果是NONPOINTER_ISA对象,那就对isa.extra_rc进行+1;
3.如果isa.extra_rc满了,就取一半复制到sideTable中辅助储存
release
release方法内部就是引用引用计数减一,就不再解释,基本和retain差不多,自己看源码
AutoReleasePool自动释放池
AutoReleasePool 是ARC引入的,用于管理对象的引用计数。
自动释放池的实现文档
Autorelease pool implementation
- A thread's autorelease pool is a stack of pointers.Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary.
- A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released.
- The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary.
- Thread-local storage points to the hot page, where newly autoreleased objects are stored.
翻译:
- 一个线程的自动释放池是一种栈形式的指针集合,先进后出;每个指针要么是要释放的对象,要么是的池边界,即自动释放池边界。
- 池token是指向该池池边界的指针。当池被弹出时,所有比哨兵还热的对象都被释放;
- 这个栈是个一个双向链表的页面列表。根据需要添加和删除页面。
- 线程本地存储指向热页,其中存储新的自动释放的对象。
相关的数据结构
AutoreleasePoolPage内部结构
class AutoreleasePoolPage;
struct AutoreleasePoolPageData {
magic_t const magic; // 16
__unsafe_unretained id *next; //8
pthread_t const thread; // 8
//证明了双向链表结构
AutoreleasePoolPage * const parent; //8
AutoreleasePoolPage *child; //8
uint32_t const depth; // 4
uint32_t hiwat; // 4
AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
: magic(), next(_next), thread(_thread),
parent(_parent), child(nil),
depth(_depth), hiwat(_hiwat)
{
}
};
AutoreleasePoolPage是个继承于AutoreleasePoolPageData结构体的类magic_t const magic;:用来校验AutoreleasePoolPage的结构是否完整__unsafe_unretained id *next: 指向最新添加的autopeleased对象的下一个位置,初始化时指向begin()pthread_t const thread:至当前线程AutoreleasePoolPage * const parent:指向父节点,第一个parent节点为nilAutoreleasePoolPage *child:指向子节点,最后一个child节点为niluint32_t const depth:代表深度,从0开始,递增+1uint32_t hiwat:代表 high water Mark 最大入栈数量标记
话外
- 第一页
AutoreleasePoolPage最多能放504个对象指针+一个特殊指针(边界) - 之后的
AutoreleasePoolPage能放505个对象指针
结构图
对象自动释放探究
示例代码
int main(int argc, char * argv[]) {
@autoreleasepool {
}
return 0;
}
转成cpp文件查看
clang -rewrite-objc main.m -o mian.cpp
mian.cpp。这里只展示部分重点代码。
struct __AtAutoreleasePool {
//构造函数
__AtAutoreleasePool() {
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
//析构函数
~__AtAutoreleasePool() {
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
int main(int argc, char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool; //__AtAutoreleasePool实例化
}
return 0;
}
可以看到原来的代码被自动加上了__AtAutoreleasePool实例化代码,并且调用了构造和析构函数。
atautoreleasepoolobj = objc_autoreleasePoolPush();
这是个构造函数,继续查看源码
//返回一个 AutoreleasePoolPage 对象
void *
objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::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;
}
######### autoreleaseFast()
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
//是否有hotPage 且没有满
if (page && !page->full()) {
//没满就添加
return page->add(obj);
} else if (page) {//有hotPage,但满了
满了就开分页;
return autoreleaseFullPage(obj, page);
} else {//没有hotPage,内部也是新的page
return autoreleaseNoPage(obj);
}
}
######### add()
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;//将next指针指向当前对象指针
protect();
return ret;
}
######### autoreleaseFullPage()
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 {//内部递归寻找最后一页(判断是否有page->child),找到后开新的page
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
//将新page置为HotPage
setHotPage(page);
return page->add(obj);
}
push简单总结
- 当对象指针被放入
pool时会先判断是否有hotPage - 有就添加,并将
next指针指向当前对象 - 没有或者
hotPage满了就创建新页再添加。创建新页时会给depth ++深度增加
objc_autoreleasePoolPop(atautoreleasepoolobj);
这里的ctxt 其实是AutoreleasePoolPage
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
############ pop()
static inline void
pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;//用于保存释放停止的标记
//判断 token(对象指针)是否是空的标识指针,是的话就代表没有对象被放入这个池子里
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
page = pageForPointer(token);
}
//将对象指针标记为停止指针
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
// 第一个节点 - 没有父节点,越界保护判断
if (stop == page->begin() && !page->parent) {
// 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);
}
//开始pop
return popPage<false>(token, page, stop);
}
######## popPage()
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
//内部进行对象release
page->releaseUntil(stop);
// 杀page,即删除空的child page,
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
######### releaseUntil() //释放对象操作
void releaseUntil(id *stop)
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage
//循环遍历,直到stop对象
while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();
//如果page空了,就拿parent page
while (page->empty()) {
page = page->parent;
setHotPage(page);
}
page->unprotect();
//next 指针 --
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();
//不等于边界指针
if (obj != POOL_BOUNDARY) {
//对象s释放
objc_release(obj);
}
}
//将当前page设置为HotPage
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简单总结
- 当要
pop对象的时候,系统会给一个token对象指针,这个指针用于指定释放的程度(位置) - 找到
token对象所在的page,并生成一个stop停止对象,然后开始pop操作 page->releaseUntil(stop):e,内部循环遍历执行对象的release,直到stop对象,并将当前page设为hotpage- 将已经释放对象所属的
page杀了,即删除空的child page
autorelease
//再来看看在ARC环境下,这个被隐藏的autorelease()方法做了什么
- (id)autorelease {
//将self放到_objc_rootAutorelease()
return _objc_rootAutorelease(self);
}
_objc_rootAutorelease(id obj) {
ASSERT(obj);
//调用rootAutorelease()
return obj->rootAutorelease();
}
id objc_object::rootAutorelease() {
//TaggedPointer 不需要管理引用计数
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
id
objc_object::rootAutorelease2() {
ASSERT(!isTaggedPointer());
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;
}
autorelease简单总结
autorelease方法其实实现和push方法一样,不同的是在push前会判断对象是否可以是TaggedPointer对象;
面试题
1.线程和autoreleasePool的关系?
- 一个线程只有一个
autoreleasePool autoreleasePool嵌套时,只会创建一个page,但是有两个池边界
写在最后
如果文章对您有帮助,烦请点个赞,小弟在此谢过了!