iOS进阶-内存管理

内存布局

先上一内存布局图 c++

解释

  • 内核区:由系统控制处理的,大概有占有1个GB
  • 栈区 :函数、方法、局部变量等会储存在这里面
  • 堆区 :经过alloc分配对象、block copy...
  • bbs区:未初始化的全局变量、静态变量...
  • data区:已初始化的全局变量、静态变量...
  • text: 程序代码
  • 保留区:由系统控制处理

其余面试

  • (0xC0000000 = 3221225472 = 3GB),因此从栈区到保留区一共占有3GB
  • 通常咱们只讨论栈区-text区这5大区域
  • 栈区向下增加,内存占有小,处理里速度快;堆区向上增加,内存占有大,处理里速度慢
  • bbs和data区在不区分是否初始化时,通常统称全局区

地址开头数组

  • 栈区内存地址:通常以 0x7开头
  • 堆区内存地址:通常以 0x6开头
  • data区、bbs区内存地址:通常以 0x1开头

面试题

  • 一、全局变量和局部变量在内存中是否有区别?若是有,是什么区别?

答:有。全局变量存在全局区(bbs区/data区),局部变量存在栈区安全

  • 二、下面Block 是否能够直接修改全局变量?
static NSString *K_Name = @"lala";
- (void)test {
    [UIView animateWithDuration:1 animations:^{
       K_Name = @"dingding";
    }];
}
复制代码

答:能够,全局变量能够全局访问bash

  • 三、下面打印是怎样的?
##### 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变量的做用域与对象、类、分类不要紧,只与文件有关系;app

内存管理方案

apple在内存管理方面提供了三种方案(TaggetPointer、NONPOINTER_ISA、散列表),严谨的说应该是三种方案共同管理内存。less

TaggetPointer

介绍

  • 一、Tagged Pointer是专⻔⽤来存储⼩的对象,例如NSNumber和NSDate
  • 二、Tagged Pointer指针的值再也不是地址了,⽽是真正的值。因此,实际上它再也不是⼀个对象了,它只是⼀个披着对象⽪的普通变量⽽已。因此,它的内存并不存储 在堆中,也不须要malloc和free
  • 三、在内存读取上有着3倍的效率,建立时⽐之前快106倍。

原理

首先理解一个操做:对同一个数值^(异或)操做两次,获得的仍是原来的数值。eg:async

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_ISAisaisa_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 内部结构

struct SideTable {
    spinlock_t slock;//锁,用于控制数据访问安全
    RefcountMap refcnts;//引用计数表s
    weak_table_t weak_table;//弱引用计数表s
    ...
    ...
};
复制代码

结构解释

  • 系统维护了一张全局Hash表,用于管理对象的引用计数和weak指针。内部就是一个个散列表SideTble,使用对象的内存地址做为SideTable的Key(地址通过哈希计算)。而且不一样的对象可能在同一张散列表SideTble中。
  • spinlock_t slock:锁,用于控制这张散列表SideTble的数据访问安全。
  • RefcountMap refcnts:引用计数表RefcountMap,用于储存对象的引用计数状况,下面会具体说明
  • weak_table_t weak_table:弱引用状况表,用于储存对象的弱引用状况,下面会具体说明

RefcountMap 引用计数表

isa_textra_rc位引用计数满了后会把一半的引用计数放到某个散列表SideTable中的引用计数表中;具体操做以下:

  • RefcountMap表内部是一个个BucketT(桶)连起来的Buckets(相似于数组),
  • 又经过对象内存的地址通过运算(这部分很复杂,我也说不太明白)获得具体BucketT(桶)
  • BucketT(桶)里面封装了对象的引用计数。以后就是引用计数的+-了

weak_table_t 弱引用计数表

  • 一、weak_table_t 是个二维数组,里面包含了一个个weak_table,weak_table里面是一个个weak_entry
  • 二、当一个对象的属性被设置成weak时,weak_table表中会查找当内部有没有该对象的弱引用数组(weak_entry数组),若是有就直接插入这个属性到这个weak_entry数组,没有就先建立weak_entry数组再插入
  • 三、当对象被释放时(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结构体的类
  • __unsafe_unretained id *next:用来校验AutoreleasePoolPage的结构是否完整
  • next: 指向最新添加的autopeleased对象的下一个位置,初始化时指向begin()
  • pthread_t const thread :至当前线程
  • AutoreleasePoolPage * const parent :指向父节点,第一个parent节点为nil
  • AutoreleasePoolPage *child:指向子节点,最后一个child节点为nil
  • uint32_t const depth:表明深度,从0开始,日后递增
  • uint32_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,内部循环遍历执行对象的releas,直到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,可是有两个池边界

timer

相关文章
相关标签/搜索