iOS面试题集合

iOS-- 内存管理

2020-09-09  本文已影响0人  Engandend

手动目录

  • 内存分布及存储
    静态变量安全
  • taggedPointer
    特点
    taggedPointer 演变
    taggedPointer 存储方式
    引用计数处理方式
    alloc 出来的对象引用计数
    dealloc 干了什么

内存分布及存储

内存分布

为什么堆区比栈区的访问速度慢?
栈区是寄存器直接读取。
堆区的访问,是寄存器先读取栈区的指针地址,然后通过这个地址去堆区找到相应的数据。

栈区内存地址:一般0x7开头
堆区内存地址:一般0x6开头
数据段、BSS段地址:一般0x1开头

// 全局变量、全局静态变量  初始化的在常量区(.data) ,未初始化的在静态区(.bss)
int clA;                                //静态区
int clB = 10;                                       //常量区
static int bssA;                        //静态区
static NSString *bssStr1;               //静态区
static int bssB = 10;                               //常量区
static NSString *bssStr2 = @"name";                 //常量区

- (void)testStack{
    int a = 10;                             // 栈区
    int b = 20;                             // 栈区
    NSObject *object = [NSObject new];      // *obj 栈区  ,  obj 堆区
    NSString *str = @"aaa";                 // *str 栈区  ,  str 常量区(.data)
    NSString *str1;                         // *str1 栈区 ,  str1  0x0
}

静态变量安全

如下代码

// Person 类  定义一个静态变量 personHei
static int personHeig = 180;
@interface Person : NSObject
- (void)growUp;
@end

@implementation Person
- (void)growUp {
    personAge =  30;
    NSLog(@"person age = %d %p",personAge,&personAge);          // person age = 30 0x10be0fe10
}
@end

// 在另外一个类中区访问并修改
- (void)task8 {
    NSLog(@"person age = %d %p",personAge,&personAge);          // person age = 18 0x10be0fc18
    personAge = 30;
    NSLog(@"person age = %d %p",personAge,&personAge);          // person age = 30 0x10be0fc18
    
    [[Person new] growUp];
    personAge ++;
    NSLog(@"person age = %d %p",personAge,&personAge);          // person age = 31 0x10be0fc18
}

//最后打印结果:
person age = 18 0x10be0fc18
person age = 30 0x10be0fc18
person age = 30 0x10be0fe10
person age = 31 0x10be0fc18

对于静态全局变量,对比内存地址和值,我们发现:
同一个文件中,访问的全局静态变量 地址相同,而且可以正常的修改
对于不同文件中的全局静态变量 地址是不同的,而且修改是相互不影响
personAge 在Person类中,地址和在另外一个类中的地址不同,而且修改之后相互不影响。

taggedPointer

taggedPointer是干嘛用的? 用于优化内存的

特点

在iPhone5s之前,苹果系统是32位,而5s出来之后,是64位,
在32位系统中,小对象(比如NSNumber、NSData)用8位去存储值都能满足大部分情况,如果还用这8位去存储一个地址指针,就太浪费内存了。苹果为了优化这个问题,而引入taggedPointer的方式。

简单来讲可以理解为把指针指向的内容直接放在了指针变量的内存地址中,因为在 64 位环境下指针变量的大小达到了 8 位足以容纳一些长度较小的内容。于是使用了标签指针这种方式来优化数据的存储方式。

taggedPointer 演变

taggedPointer 存储方式

最后一位表示的含义:

typedef uint16_t objc_tag_index_t;
enum
#endif
{
    // 60-bit payloads
    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    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
};

引用计数处理方式

如何维护引用计数呢?

在之前的的 isa结构探究 中 提到 isa结构中,有引用计数的记录。

extra_rc :19
当表示该对象的引用计数值,实际上是引用计数值减1,例如,如果对象的引用计数为10,那么extra_rc为9.如果引用计数大于10,则需要使用上面提到的has_sidetable_rc。

那么在对象进行retain的时候, 那么具体是进行了什么操作呢?

id 
objc_retain(id obj)
{
    if (!obj) return obj;
    if (obj->isTaggedPointer()) return obj;              // ⚠️ 如果是  taggedPointer ,就不进行retain
    return obj->retain();
}


ALWAYS_INLINE id 
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
    if (isTaggedPointer()) return (id)this;

    bool sideTableLocked = false;
    bool transcribeToSideTable = false;

    isa_t oldisa;
    isa_t newisa;

    do {
        transcribeToSideTable = false;
        oldisa = LoadExclusive(&isa.bits);
        newisa = oldisa;
        if (slowpath(!newisa.nonpointer)) {                    // 如果不是 nonpoint isa  直接存散列表
            ClearExclusive(&isa.bits);
            if (rawISA()->isMetaClass()) return (id)this;
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
            else return sidetable_retain();
        }
        // don't check newisa.fast_rr; we already called any RR overrides
        if (slowpath(tryRetain && newisa.deallocating)) {
            ClearExclusive(&isa.bits);
            if (!tryRetain && sideTableLocked) sidetable_unlock();
            return nil;
        }
        uintptr_t carry;
        newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++            // 如果是nonpoint isa  就进行isa 的extra_rc++          操作

        if (slowpath(carry)) {          // 如果isa的  extra_rc  超过 容量,
            // 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.
            if (!tryRetain && !sideTableLocked) sidetable_lock();
            sideTableLocked = true;
            transcribeToSideTable = true;
            newisa.extra_rc = RC_HALF;                  // isa 中的 extra_rc  保存一半
            newisa.has_sidetable_rc = true;
        }
    } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

    if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);            // 散列表中的引用计数表 存一半
    }

    if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
    return (id)this;
}

引用计数的处理 retain

  • 如果不是 nonpoint isa 直接加入散列表
  • 如果是 nonpoint isa 在isa的 extra_rc 进行++操作
    如果超过容量,isa 中的 extra_rc 保存一半 散列表中的引用计数表 存一半

引用计数的处理 release

  • 如果不是 nonpoint isa 直接对散列表进行操作
  • 如果是 nonpoint isa 在isa的 extra_rc 进行-- 操作
    如果减到最后没了,看有没有相应的散列表的引用计数表,如果有,把引用计数表的计数赋值给extra_rc。

补充:
散列表有多张(据说最多64张),每张散列表维护 三张表,多表结构的目的是为了:安全、高效。对表进行操作,需要加锁/解锁,同时可能由多个任务需要加锁/解锁。所以用多表可以提高查询速度、提高执行速度。

struct SideTable {
    spinlock_t slock;             // 自旋锁
    RefcountMap refcnts;  //引用计数表
    weak_table_t weak_table;    // 弱引用表1
}

alloc 出来的对象引用计数

这也是一个面试题:alloc出来的对象,其引用计数是多少?
答案是0。

在之前的iOS底层-alloc与init 中详细的说了alloc的过程,在此过程中,我们并没有看到任何与引用计数有关的内容,也就是说,并没有操作引用计数,所以为0 。

但是 为什么通过打印的方式得到的是1 呢?
printf("Retain Count = %ld\n",CFGetRetainCount((__bridge CFTypeRef)(obj)));

通过源码来看:

inline uintptr_t 
objc_object::rootRetainCount()
{
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    ClearExclusive(&isa.bits);
    if (bits.nonpointer) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

打印出来的引用计数值的又来: 1 + isa.extra_rc + sidetable_rc(散列表的引用计数)
既然打印结果为1,那么 其 isa.extra_rc 必然为0, 所以其真正的引用计数为0。

为什么默认要给个1呢,因为 它要被autorelease(自动释放池)所持有。

dealloc 干了什么

看源码:

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;
    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();              //  非 nonpointer 散列表清空
    }
    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());
}

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

dealloc 干了什么

  • 清空关联对象
  • 清空 弱引用对象
  • 清空 散列表中的引用计数表
上一篇下一篇

猜你喜欢

热点阅读