iOS面试总结

内存管理

2019-06-20  本文已影响0人  Jimmy_L_Wang

内存布局

内存布局.png

内存管理方案

iOS是如何对内存进行管理的?

NONPOINTER_ISA结构

arm64架构

nonpointer_isa01.png nonpointer_isa02.png

散列表方式

SideTables()源码
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
SideTables()结构
sideTable.png

side tables实际上是一个hash表,通过一个对象指针,找到他对应的引用计数表,或弱引用表。

Side Table

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);
};

SideTable结构

sideTable02.png

为什么不是一个side table?

one_side_table03.png

假如说只有一张side table,相当于我们在内存当中分配的所有对象的引用计数或者说弱引用存储都放在一张大表当中,这个时候如果我们要操作某一个对象的引用计数值进行修改,比如说进行加1或减1的操作的话,由于所有的对象可能是在不同的线程当中去分配创建的,包括调用他们的release,retain等方法,也可能是在不同的线程当中进行操作;这个时候对一种表进行操作的时候,需要进行加锁处理,才能保证对于数据的访问安全,在这个过程中就存在了一个效率问题。比如说用户的内存空间一共有4GB,那么可能分配出成千上百万个内存对象,如果说每一个对象在对他进行内存引用计数的改变的时候,都操作这张表很显然就会有效率的问题。如果说已经又一个对象在操作这张表,下一个对象就要等他操作完,把锁释放之后再进行操作,这效率就会太低了。

one_side_table04.png

系统为了解决效率问题,引入了分离锁的技术方案。我们可以把内存对象所对应的引用计数表,可以分拆成多个部分。比如说分拆成8个,需要对8个表分别加锁。当A和B同时进行引用计数操作的话可以进行并发操作,如果是一张表他们需要进行顺序操作。很明显分离锁可以提高访问效率。

怎样实现快速分流?

快速分流指通过一个对象的指针如何快速定位到它属于那张side table 表当中。

hash表.png

side tables的本质是一张Hash表。这张hash表当中,可能有64张具体的side table 存储不同对象的引用计数表和弱引用表。

自旋锁 Spinlock_t

引用计数表RefcountMap

引用计数表实际上是一个hash表,我们可以通过指针来找到对应对象的引用天计数,这一过程实际上也是hash查找(使用hash查找是为了提高查找效率)。

refcount_map.png

插入和获取是通过同一个hash函数完成,避免了递归查找和for循环遍历

size_t内存分配

size_t.png

弱引用表weak_table_t

weak_table_t实际上也是一个hash表.

weak_table_t.png

weak_entry_t实际上是一个结构体数组。结构体数组存储的是每一个的弱引用指针,也就是代码当中定义的__weak id obj,obj内存地址即指针就存储在weak_entry_t

MRC & ARC

MRC 手动引用计数

ARC 自动引用计数

ARC实际是由编译期自动为我们插入retainrelease操作之外,还需要runtime的功能进行支持,然后由编译器和Runtime共同协作才能组成ARC的全部功能。

引用计数管理

实现原理分析

alloc实现

retain实现

id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
  //hash查找SideTable
    SideTable& table = SideTables()[this];
    
  //SideTable加锁
    table.lock();
  //hash查找引用计数值
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
      //引用计数加1
      //#define SIDE_TABLE_RC_ONE            (1UL<<2)
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}
  1. 通过当前对象的指针this,经过hash函数的计算,可以快速的SideTables当中找到(hash查找)它对应的SideTable
  2. 然后在SideTable当中获取应用计数map这个成员变量,通过对象的指针this,在SideTable的引用计数表中获取(hash查找)当前当前对象的引用计数值。
  3. 经过一定的条件判断之后,引用计数加1。

引用计数加1,实际是加上了偏移量对应的操作,这个偏移量是4,反应出来的结果是加1,因为size_t64位,前两位不是存储引用计数,所以需要向左偏移两位操作1UL<<2

release实现

uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
  //hash查找SideTable
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

  //加锁
    table.lock();
  //根据当前对象指针,访问table的应用计数表
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it == table.refcnts.end()) {
        do_dealloc = true;
        table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
    } else if (it->second < SIDE_TABLE_DEALLOCATING) {
        // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
        do_dealloc = true;
        it->second |= SIDE_TABLE_DEALLOCATING;
    } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
      //引用计数减1操作
        it->second -= SIDE_TABLE_RC_ONE;
    }
    table.unlock();
    if (do_dealloc  &&  performDealloc) {
        ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
    }
    return do_dealloc;
}
  1. 通过当前对象的指针this,经过hash函数的计算,可以快速的SideTables当中找到(hash查找)它对应的SideTable
  2. 根据当前对象指针,访问table的应用计数表
  3. 找到对应的值进行引用计数减1操作

retainCount实现

uintptr_t
objc_object::sidetable_retainCount()
{
  //hash查找SideTable
    SideTable& table = SideTables()[this];
  //声明局部变量赋值为1
    size_t refcnt_result = 1;
    
  //加锁
    table.lock();
  //根据当前对象指针,访问table的应用计数表
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
      //查找结果向右位移2位,再加上局部变量的值
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}
  1. 通过当前对象的指针this,经过hash函数的计算,可以快速的SideTables当中找到(hash查找)它对应的SideTable

  2. 根据当前对象指针,访问table的应用计数表

  3. 找到对应的值向右位移2位,再加上局部变量的值1

    这就是alloc操作之后,引用计数没有变化,但retainCount获取的值是1的原因

dealloc实现源码

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
    assert(obj);

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

inline void 
objc_object::clearDeallocating()
{
    if (slowpath(!isa.nonpointer)) {
        // Slow path for raw pointer isa.
        sidetable_clearDeallocating();
    }
    else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {
        // Slow path for non-pointer isa with weak refs and/or side table data.
        clearDeallocating_slow();
    }

    assert(!sidetable_present());
}

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
    assert(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));

    SideTable& table = SideTables()[this];
    table.lock();
    if (isa.weakly_referenced) {
        weak_clear_no_lock(&table.weak_table, (id)this);
    }
    if (isa.has_sidetable_rc) {
        table.refcnts.erase(this);
    }
    table.unlock();
}

void 
objc_object::sidetable_clearDeallocating()
{
    SideTable& table = SideTables()[this];

    // clear any weak table items
    // clear extra retain count and deallocating bit
    // (fixme warn or abort if extra retain count == 0 ?)
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        table.refcnts.erase(it);
    }
    table.unlock();
}

dealloc实现流程图
dealloc.png
object_dispose()实现
object_dispose()实现.png
objc_destructInstance()实现
objc_destructInstance()实现.png
clearDeallocating()实现
clearDeallocating实现.png

弱引用管理

弱引用管理.png
id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
        (location, (objc_object*)newObj);
}

添加weak变量的弱引用实现

weak调用栈.png

当一个对象被释放或者废弃之后,weak变量怎样处理的?

/** 
 * Called by dealloc; nils out all weak pointers that point to the 
 * provided object so that they can no longer be used.
 * 
 * @param weak_table 
 * @param referent The object being deallocated. 
 */
void 
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
    objc_object *referent = (objc_object *)referent_id;

    weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
    if (entry == nil) {
        /// XXX shouldn't happen, but does with mismatched CF/objc
        //printf("XXX no entry for clear deallocating %p\n", referent);
        return;
    }

    // zero out references
    weak_referrer_t *referrers;
    size_t count;
   
    if (entry->out_of_line()) {
        referrers = entry->referrers;
        count = TABLE_SIZE(entry);
    } 
    else {
        referrers = entry->inline_referrers;
        count = WEAK_INLINE_COUNT;
    }
    
    for (size_t i = 0; i < count; ++i) {
        //referrers取到弱引用指针的所有对应的数组列表
        objc_object **referrer = referrers[I];
        if (referrer) {//如果referrer即弱引用指针存在
            if (*referrer == referent) { //如果弱引用指针对应的是被废弃的对象的话,就将指针置为nil
                *referrer = nil;
            }
            else if (*referrer) {
                _objc_inform("__weak variable at %p holds %p instead of %p. "
                             "This is probably incorrect use of "
                             "objc_storeWeak() and objc_loadWeak(). "
                             "Break on objc_weak_error to debug.\n", 
                             referrer, (void*)*referrer, (void*)referent);
                objc_weak_error();
            }
        }
    }
    
    weak_entry_remove(weak_table, entry);
}

当一个对象被dealloc之后,内部实现当中会去调用weak_clear_no_lock()函数,函数实现内部会根据弱引用指针查找弱引用表把当前对象相对应的弱引用拿出来,然后遍历数组的所有弱引用指针,分别置为nil

自动释放池

编译期会将代码块@autoreleasepool{}改写为:

  1. void *ctx = objc_autoreleasePoolPush();

  2. {}中的代码

  3. objc_autoreleasePoolPop(ctx);

    一次pop实际上相当于一次批量的pop操作

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

void
objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

自动释放池的数据结构

双向链表

双向链表.png

栈结构.png

AutoreleasePoolPage类源码

class AutoreleasePoolPage 
{
    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;//指向当前栈中下一个可填充的位置
    pthread_t const thread; //线程
    AutoreleasePoolPage * const parent;//双向链表的父指针
    AutoreleasePoolPage *child;//双向链表的子指针
    uint32_t const depth;
    uint32_t hiwat;

    // SIZE-sizeof(*this) bytes of contents follow

    static void * operator new(size_t size) {
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    static void operator delete(void * p) {
        return free(p);
    }
  
  .....
}

AutoreleasePoolPage结构

autoreleasePoolPage.png

AutoreleasePoolPage::push

autoreleasePoolPage_push.png

[obj autorelease]

obj_autorelease.png

AutoreleasePoolPage::pop

autoreleasePoolPage_pop01.png
autoreleasePoolPage_pop02.png

总结

- (void)viewDidLoad
{
  [super viewDidLoad];
  NSMutableArray *array = [NSMutableArray array];
  NSLog(@"%@",array);
}

上面array的内存在什么时候释放的?

当每次runloop将要结束的时候,都会对前一次创建的AutoreleasePool调用AutoreleasePoolPage::pop()操作,同时会push进来一个AutoreleasePool。所以array对象会在当前runloop就要结束的时候调用AutoreleasePoolPage::pop()方法,把对应的array对象,调用其release函数对其进行释放

AutoreleasePool的实现原理是怎样的?

是以为节点通过双向链表的形式组合而成的数据结构

AutoreleasePool为何可以嵌套使用?

多层嵌套就是多次插入哨兵对象。在我们每次创建代码块@autoreleasepool{},系统就会为我们进行哨兵对象的插入,完成新的AutoreleasePool的创建,实际上也是创建了一个AutoreleasePoolPage,假如当前AutoreleasePoolPage没有满的话,就不用创建AutoreleasePoolPage。所以新创建的AutoreleasePool底层就是插入一个哨兵对象,所以可以多层嵌套。

循环引用

三种循环引用

自循环引用

自循环引用.png

相互循环引用

相互循环引用.png

多循环引用

多循环引用.png

循环引用考点

如何破除循环引用?

破除循环引用具体的解决方案都有哪些?

__weak破解

weak破解.png

__block破解

__unsafe_unretained破解

循环引用示例

nstimer方案.png
#import "NSTimer+WeakTimer.h"

@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;
}

@end

内存管理面试总结

上一篇 下一篇

猜你喜欢

热点阅读