iOS 内存管理

在开发中,内存管理是一个必要的技能,研究iOS 开发,我们通过内存布局、内存管理方案、数据结构、ARC/MRC、引用计数、弱引用、自动释放池、循环引用这个八个方面去了解iOS 的内存管理。

内存布局

内存布局

Stack:方法调用
Heap:通过alloc分配的对象
Bss:未初始化的静态变量和全局变量
Data:已初始化的全局变量
Test:程序代码

内存管理方案

isa指针

OC对象里提到最多的就是isa指针了,大家都知道isa是指向该对象的内存地址,其实这个地址里有一些特殊的含义。
在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
从arm64架构开始,对isa进行了优化,变成了一个共用体(union)结构,还使用位域来存储更多的信息


  • 1位:nonpointer,如果是0就代表着是一个纯的isa指针,直接代表了当前类对象的直接地址,如果是1代表着不仅存储着地址,而且还包含着一些内存管理方面的数据,就是非指针类型的isa.
  • 2位:has_assoc,0和1代表着是否含有关联对象。如果没有,释放时会更快。
  • 3位:has_cxx_dtor,是否有C++的析构函数(.cxx_destruct),通过这个也能代表是通过ARC来进行内存管理的。如果没有,释放时会更快。
  • 接下来33位:shiftcls,代表了当前类对象的地址,需要把对应位置的值拿出来,再去计算实际对应的指针地址。存储着Class、Meta-Class对象的内存地址信息
  • 后6位:magic用于在调试时分辨对象是否未完成初始化
  • 1位:weakly_refrenced,标识了该对象是否有弱引用指针。如果没有,释放时会更快。
  • 1位:deallocting,标识了当前对象是否在进行dealloc操作
  • 1位:has_sidetable_rc,标识了如果当前对象如果已经超过了引用计数上限,需要外挂一个sidetable去存储相关的引用计数内容,就是一个散列表
  • 后续位:extra_rc,代表了额外的引用计数,当我们的引用计数在一个很小的范围内就会存在isa指针当中,而不是存在另一个表里。
    关于内存管理不仅仅有散列表,其实还有extra_rc来存储相关的引用计数值。
散列表内存管理方案

iOS中是主要通过散列表去管理内存。源码当中利用SideTables()结构来实现,sidetables实际上是一个哈希表,我们可以通过引用对象指针,来找到对应的sidetable.
SideTables结构

这里设计成多个sideTable的思想是,如果有一个table的话,我们在程序内申请的所有对象的引用计数或者弱引用存储就会放在一个大表中,这个时候如果我们要操作某个对象的引用值去进行修改,包括release,retain,而且对象都是在不同的线程中,这个时候对表操作的时候需要进行加锁去处理,会十分影响效率。系统为了解决这个问题,引用了分离锁的计数方案。
分离锁:我们可以把内存对象对应的引用计数表,拆成多个部分,这样对多个对象操作时候,就可以分散对多个表分别加锁。比如某个对象在A表里,另个对象在B表里,那如果两个对象要同时进行引用计数操作的时候,就可以并发操作,如果在一张表里就需要挨着顺序去操作表。

实现快速分流

那怎么去通过isa指针去快速的定位到是哪个sidetable表中,这里就提到了快速分流的方案Hash查找。
sidetables的本质是一张hash表,一共有64张,来存储引用计数。
hash的具体操作,就是以对象指针作为key,通过hash函数,获得对应的sidetable作为value。我们回想一下OC中字典的使用方法,就基本对哈希有一个大概的认识。

数据结构

spinlock_t自旋锁

是忙等的锁,忙等指的是如果当前线程已被其他锁获取,那么当前线程就会不断的探索锁是否被释放,如果释放掉了,会去第一时间获取这个锁。自旋锁适合轻量访问。

refcountTable引用计数表

引用计数表实际上就是哈希算法的使用,通过哈希查找去定位引用计数的值,这个值就是一个unsigned long 类型值,共64位

  • 1位 weakly_referenced,表示是否有弱引用。
  • 2位 deallocing 表示是否正在释放
  • 其他位数 代表了引用计数值,实际上使用的时候需要向右平移两位,才能算出正确的值
weak_table_t弱引用表

弱引用表也是一个哈希表,通过key去查找value,这里的value是一个结构体数组


弱引用表

ARC MRC

这里只提一嘴,ARC实际上是有编译器llvm和runtime共同作用才可以

引用计数管理

  • alloc实现
    经过一系列调用,最终调用了c函数calloc
    此时并没有设置引用计数为1
  • retain实现,实际上是两次哈希查找
SideTable& table = SideTables()[this] // 第一次哈希查找是从所有表中定位出当前对象引用计数所在的那张表
size_t& refcntStorage = table.refcnts[this]// 第二次哈希是从表中找出引用计数的值
refcntStorage += SIDE_TABLE_RC_ONE // 这个函数是增加引用计数,SIDE_TABLE_RC_ONE这个常量通过前边提到的引用计数值的讲解可以知道这个值是4,因为64位中后两位并不是引用计数的含义
  • release实现,方式同retain
SideTable& table = SideTables()[this] 
RefcountMap::iterator it = table.fefcnts.find(this)
refcntStorage += SIDE_TABLE_RC_ONE
  • retainCount实现,从代码中可以看出alloc的情况下,虽然引用计数没有设置为1,但是retainCount的结果依然唯一,就是因为有refcnt_result的存在
SideTable& table = SideTables()[this] ;
size_t refcnt_result = 1;
RefcountMap::iterator it = table.fefcnts.find(this)
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT
  • dealloc实现


    dealloc实现
object_dispose实现
objc_destructInstance实现
clearDeallpcating实现

弱引用管理

id __weak obj1 = obj; //编译前
id obj1; obj_initWeak(&obj1, obj)//编译后
  • 添加weak变量调用栈:
    obj_initWeak()->storeWeak()->weak_register_no_lock()
    通过NSObject的源码可以看到是按上边的三个函数的调用顺序来处理,最终是由weak_register_no_lock函数进行弱引用变量的添加,具体添加的位置是通过hash算法来查找,如果所查找的对象已经查找到了所对应的弱引用数组,那么就进行数组的添加,如果没有查找到就新建一个数组,添加到第0个位置,后边的位置初始化为Nil。
  • 清除weak变量,同时设置指向为nil
    dealloc()->...->weak_clear_no_lock()
    通过源码可以看出,当一个对象在dealloc之后,会调用weak_clear_no_lock方法,首先对当前对象进行一个哈希查找,如果没有在弱引用表中找到这个弱引用数组,那么直接返回,如果找到了,就遍历这个数组,挨个设置为Nil。

自动释放池AutoreleasePool

首先编译器会将@autoreleasepool{}改写为:

void * ctx = obj_autoreleasePoolPush(); 这个函数会调用void *AtuorelasePoolPage::push(void)
中间是{}中的代码
之后objc_autoreleasePoolPop(ctx); 这个函数会调用 AutorelasePoolPage::pop(void *ctxt)

一次pop实际上相当于一次批量的pop操作,就是说添加到autoreleasepool{}内的变量,会在pop时候一次都释放掉。

自动释放池的数据结构
  • 是以栈为节点通过双向链表的形式组合而成
    AotoreleasePoolPage中包含的结构信息
    id *next; //指向栈当中下一个可填充的位置。
    AotoreleasePoolPage *const parent;//双向链表中的父指针
    AotoreleasePoolPage *child;//孩子指针
    pthread_t const thread;//线程
  • AutoreleasePoolPage::push



  • AutoorelleaasePoolPage::pop
    根据传入的哨兵对象找到对应位置
    给上次push操作之后添加的对象依次发送release消息
    回退next指针到正确位置
  • 是和线程一一对应的
    在当次runloop将要结束的时候调用AutoreleasePoolPage::pop()
    AutoreleasePool可以多层嵌套的原因就是多层嵌套就是插入哨兵对象
demo
函数中加入一个autoreloasePool时候,会在函数的开始和结束分别加入push和pop方法,这个是在编译期就决定的
int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        atautoreleasepoolobj = objc_autoreleasePoolPush();
        
        for (int i = 0; i < 1000; i++) {
            MJPerson *person = [[[MJPerson alloc] init] autorelease];
        } // 8000个字节
        
//        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
}
 // 下边是将.m类编译成c++文件后的源码,编译器会生成__AtAutoreleasePool的c++代码,
 struct __AtAutoreleasePool {
    __AtAutoreleasePool() { // 构造函数,在创建结构体的时候调用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
 
    ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
 
    void * atautoreleasepoolobj;
 };
 
// main的核心代码
 {
    __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"));
 }
 
 
    atautoreleasepoolobj = objc_autoreleasePoolPush();
 
    MJPerson *person = [[[MJPerson alloc] init] autorelease];
 
    objc_autoreleasePoolPop(atautoreleasepoolobj);

循环引用

三种循环引用
  • 自循环引用
    id __strong obj = self;
  • 相互循环引用
    在B类中:id __strong obj = A;
    在A类中:id __strong obj = B;
  • 多循环引用
    A引用B,B引用C,C引用D,D引用A
如何破除循环引用
  • 避免循环引用
    __weak
    __block:MRC下,__block修饰对象不回增加引用计数,避免了循环引用。在ARC下,__block修饰对象会被强引用,无法避免循环引用,需要手动解环。
    __unsafe_unretained:修饰对象不回增加引用计数,避免了循环引用,如果被修饰对象在某一时机被释放,会产生悬垂指针。
  • 在合适的时机手动断环
NSTimer的循环引用问题

增加一个中间对象,持有NSTimer和原对象的弱引用。在中间对象里持有的target进行判断,如果值存在就说明没被释放,就把NSTimer的值回调给target.

@interface TimerWeakObject : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;

- (void)fire:(NSTimer *)timer;
@end

@implementation TimerWeakObject
- (void)fire:(NSTimer *)timer
{
    if (self.target) {
        if ([self.target respondsToSelector:self.selector]) {
            [self.target performSelector:self.selector withObject:timer.userInfo];
        }
    }
    else{
        [self.timer invalidate];
    }
}

@end

@implementation NSTimer (WeakTimer)

+ (NSTimer *)scheduledWeakTimerWithTimeInterval:(NSTimeInterval)interval
                                         target:(id)aTarget
                                       selector:(SEL)aSelector
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats
{
    TimerWeakObject *object = [[TimerWeakObject alloc] init];
    object.target = aTarget;
    object.selector = aSelector;
    object.timer = [NSTimer scheduledTimerWithTimeInterval:interval target:object selector:@selector(fire:) userInfo:userInfo repeats:repeats];
    
    return object.timer;
}

你可能感兴趣的:(iOS 内存管理)