iOS基本功春天来咯iOS Kit

iOS 内存管理

2019-03-10  本文已影响28人  大冯宇宙

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

内存布局

内存布局

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

内存管理方案

isa指针

OC对象里提到最多的就是isa指针了,大家都知道isa是指向该对象的内存地址,其实这个地址里有一些特殊的含义。在64arm中,isa占了8个字节,就是64个byte,每个byte其实都有他的含义。

散列表内存管理方案
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位

weak_table_t弱引用表

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


弱引用表

ARC MRC

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

引用计数管理

SideTable& table = SideTables()[this] // 第一次哈希查找是从所有表中定位出当前对象引用计数所在的那张表
size_t& refcntStorage = table.refcnts[this]// 第二次哈希是从表中找出引用计数的值
refcntStorage += SIDE_TABLE_RC_ONE // 这个函数是增加引用计数,SIDE_TABLE_RC_ONE这个常量通过前边提到的引用计数值的讲解可以知道这个值是4,因为64位中后两位并不是引用计数的含义
SideTable& table = SideTables()[this] 
RefcountMap::iterator it = table.fefcnts.find(this)
refcntStorage += SIDE_TABLE_RC_ONE
SideTable& table = SideTables()[this] ;
size_t refcnt_result = 1;
RefcountMap::iterator it = table.fefcnts.find(this)
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT
object_dispose实现 objc_destructInstance实现 clearDeallpcating实现

弱引用管理

id __weak obj1 = obj; //编译前
id obj1; obj_initWeak(&obj1, obj)//编译后

自动释放池AutoreleasePool

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

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

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

自动释放池的数据结构

循环引用

三种循环引用
如何破除循环引用
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;
}
上一篇 下一篇

猜你喜欢

热点阅读