当前位置: 首页>前端>正文

1.内存管理

iOS 内存管理

  1. iOS 内存管理的理解?
    实际上是三种方案的结合
    1.1 TaggedPointer(针对类似于NSNumber 的小对象类型)
    1.2 NONPOINTER_ISA(64位系统下)
  • 第一位的 0 或 1 代表是纯地址型isa 指针,还是NONPOINTER_ISA 指针。
  • 第二位,代表是否有关联对象
  • 第三位代表是否有 C++ 代码。
  • 接下来33位代表指向的内存地址
  • 接下来有 弱引用 的标记
  • 接下来有是否 delloc 的标记....等等
    1.3 散列表(引用计数表、weak表)
  • SideTables 表在 非嵌入式的64位系统中,有 64张 SideTable
  • 每一张 SideTable 主要是由三部分组成。自旋锁、引用计数表、弱引用表。
  • 全局的 引用计数 之所以不存在同一张表中,是为了避免资源竞争,解决效率的问题。
  • 引用计数表 中引入了 分离锁的概念,将一张表分拆成多个部分,对他们分别加锁,可以实现并发操作,提升执行效率
在 isa_t 联合体中不仅仅表明了指向对象的地址信息,而且这个 64 位数据还记录了其 bits 情况以及该实例每一位保存的对象信息
union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }

    Class cls;
    uintptr_t bits;
    struct {
        uintptr_t indexed           : 1;
        uintptr_t has_assoc         : 1;
        uintptr_t has_cxx_dtor      : 1;
        uintptr_t shiftcls          : 33; 
        uintptr_t magic             : 6;
        uintptr_t weakly_referenced : 1;
        uintptr_t deallocating      : 1;
        uintptr_t has_sidetable_rc  : 1;
        uintptr_t extra_rc          : 19;
    };
};


  1. 使用自动引用计数应遵循的原则?
  • 不能使用 retainreleaseretainCountautorelease
  • 不可以使用 NSAllocateObjectNSDeallocateObject
  • 必须遵守内存管理方法的命名规则。
  • 不需要显示的调用 Dealloc
  • 使用 @autoreleasePool 来代替 NSAutoreleasePool
  • 不可以使用区域 NSZone
  • 对象性变量不可以作为 C 语言的结构体成员。
  • 显示转换idvoid*
  1. ARC 自动内存管理的原则?
  • 自己生成的对象,自己持有
  • 非自己生成的对象,自己可以持有
  • 自己持有的对象不再需要时,需要对其进行释放
  • 非自己持有的对象无法释放
  1. 访问__weak修饰的变量,是否已经被注册在了 @autoreleasePool中?为什么?
  • 答案是肯定的,_weak修饰的变量属于弱引用,如果没有被注册到 @autoreleasePool 中,创建之后也就会随之销毁,为了延长它的生命周期,必须注册到 @autoreleasePool 中,以延缓释放。
  1. ARCretainCount 怎么存储的?
  • 存在64张哈希表中,根据哈希算法去查找所在的位置,无需遍历,十分快捷
  • 散列表(引用计数表、weak表) - SideTables 表在 非嵌入式的64位系统中,有 64张 SideTable 表 - 每一张 SideTable 主要是由三部分组成。自旋锁引用计数表弱引用表。 - 全局的 引用计数 之所以不存在同一张表中,是为了避免资源竞争,解决效率的问题。 - 引用计数表 中引入了 分离锁的概念,将一张表分拆成多个部分,对他们分别加锁,可以实现并发操作,提升执行效率
    引用计数表(哈希表)
  • 通过指针的地址,查找到引用计数的地址,大大提升查找效率
  • 通过 DisguisedPtr(objc_object) 函数存储,同时也通过这个函数查找,这样就避免了循环遍历。
struct SideTable {
    // 保证原子操作的自旋锁
    spinlock_t slock;
    // 引用计数的 hash 表
    RefcountMap refcnts;
     // weak 引用全局 hash 表
    weak_table_t weak_table;
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
…..
    }
}
  1. @autoreleasePool的数据结构?
  • 简单说是双向链表,每张链表头尾相接,有 parentchild指针
  • 每创建一个池子,会在首部创建一个 哨兵 对象,作为标记
  • 最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表
  1. __weak_Unsafe_Unretain 的区别?
  • weak 修饰的指针变量,在指向的内存地址销毁后,会在 Runtime 的机制下,自动置为nil
  • _Unsafe_Unretain不会置为nil,容易出现 悬垂指针,发生崩溃。但是 _Unsafe_Unretain__weak效率高。
  1. 为什么已经有了ARC ,但还是需要@AutoreleasePool 的存在?
  • 避免内存峰值,及时释放不需要的内存空间
  1. __weak 属性修饰的变量,如何实现在变量没有强引用后自动置为nil
  • 用的弱引用 - weak表。也是一张 哈希表。
  • weak 修饰的指针变量所指向的地址是key ,所有指向这块内存地址的指针会被添加在一个数组里,这个数组是Value。当内存地址销毁,数组里的所有对象被置为 nil
  1. retain,copy,assign,weak,_Unsafe_Unretain 关键字的理解?
Strong

Strong 修饰符表示指向并持有该对象,其修饰对象的引用计数会加1。该对象只要引用计数不为0就不会被销毁。当然可以通过将变量强制赋值 nil 来进行销毁。

Weak

weak 修饰符指向但是并不持有该对象,引用计数也不会加1。在 Runtime 中对该属性进行了相关操作,无需处理,可以自动销毁。weak用来修饰对象,多用于避免循环引用的地方。weak 不可以修饰基本数据类型。

assign

assign主要用于修饰基本数据类型, 例如NSIntegerCGFloat,存储在栈中,内存不用程序员管理。assign是可以修饰对象的,但是会出现问题。

copy

copy关键字和 strong类似,copy 多用于修饰有可变类型的不可变对象上 NSString,NSArray,NSDictionary上。

__unsafe_unretain

__unsafe_unretain 类似于 weak ,但是当对象被释放后,指针已然保存着之前的地址,被释放后的地址变为 僵尸对象,访问被释放的地址就会出问题,所以说他是不安全的。

__autoreleasing

将对象赋值给附有 __autoreleasing修饰的变量等同于 ARC 无效时调用对象的 autorelease 方法,实质就是扔进了自动释放池。

  1. ARC 在编译时做了哪些工作?
  • 根据代码执行的上下文语境,在适当的位置插入 retainrelease
  1. ARC 在运行时做了哪些工作?
  • 主要是指 weak 关键字。weak 修饰的变量能够在引用计数为0 时被自动设置成 nil,显然是有运行时逻辑在工作的。
  • 为了保证向后兼容性,ARC 在运行时检测到类函数中的 autorelease 后紧跟其后 retain,此时不直接调用对象的 autorelease 方法,而是改为调用 objc_autoreleaseReturnValueobjc_autoreleaseReturnValue 会检视当前方法返回之后即将要执行的那段代码,若那段代码要在返回对象上执行retain操作,则设置全局数据结构中的一个标志位,而不执行autorelease操作,与之相似,如果方法返回了一个自动释放的对象,而调用方法的代码要保留此对象,那么此时不直接执行retain,而是改为执行 objc_retainAoutoreleasedReturnValue函数。此函数要检测刚才提到的标志位,若已经置位,则不执行 retain 操作,设置并检测标志位,要比调用 autoreleaseretain更快。
  1. 函数返回一个对象时,会对对象 autorelease 么?为什么?
  • 会 ,为了延长返回对象的生命周期,给其他使用者留足调用的时间
  1. 说一下什么是 悬垂指针?什么是 野指针?
悬垂指针

指针指向的内存已经被释放了,但是指针还存在,这就是一个 悬垂指针 或者说 迷途指针

野指针

没有进行初始化的指针,其实都是 野指针

  1. 内存管理默认的关键字是什么?
MRC
@property (atomic,readWrite,retain) UIView *view;
ARC
@property (atomic,readWrite,strong) UIView *view;

如果改为基本数据类型,那就是 assign

  1. 内存中的5大区分别是什么?
  • 栈区(stack):由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其 操作方式类似于数据结构中的栈。
  • 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
  • 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的 全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。
  • 文字常量区:常量字符串就是放在这里的。 程序结束后由系统释放。
  • 程序代码区:存放函数体的二进制代码。
  1. 是否了解 深拷贝浅拷贝 的概念,集合类深拷贝如何实现
  • 深拷贝内存拷贝浅拷贝指针拷贝
  • 集合类深拷贝通过归档、解档实现。
  1. BAD_ACCESS在什么情况下出现?
  • 访问了已经被销毁的内存空间,就会报出这个错误。 根本原因是有 悬垂指针没有被释放
  1. 讲一下 @dynamic关键字?
  • @dynamic 意味着编译器不会帮助我们自动合成 settergetter 方法。我们需要手动实现、这里就涉及到 Runtime 的动态添加方法的知识点。
  1. autoReleasePool 什么时候释放?
  • App启动后,苹果在主线程RunLoop 里注册了两个 Observer,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()

  • 第一个 Observer监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush()创建自动释放池。其 order-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

  • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush()释放旧的池并创建新池;Exit(即将退出Loop) 时调用_objc_autoreleasePoolPop()来释放自动释放池。这个Observerorder2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄漏,开发者也不必显示创建Pool

  1. retainrelease 的实现机制?

Retain的实现机制。

SideTable& table = SideTables()[This];

size_t& refcntStorage = table.refcnts[This];

refcntStorage += SIZE_TABLE_RC_ONE;

Release的实现机制。

SideTable& table = SideTables()[This];

size_t& refcntStorage = table.refcnts[This];

refcntStorage -= SIZE_TABLE_RC_ONE;

二者的实现机制类似,概括讲就是通过第一层 hash 算法,找到 指针变量 所对应的 sideTable。然后再通过一层 hash算法,找到存储 引用计数size_t,然后对其进行增减操作。retainCount 不是固定的 1,SIZE_TABLE_RC_ONE 是一个宏定义,实际上是一个值为 4 的偏移量。

  1. 能不能简述一下Dealloc 的实现机制?
    Dealloc 的实现机制是内容管理部分的重点,把这个知识点弄明白,对于全方位的理解内存管理的只是很有必要。

Dealloc 调用流程

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

NONPointer_ISA
weakly_reference
has_assoc
has_cxx_dtor
has_sidetable_rc

  • 如果有以上五中任意一种,将会调用 object_dispose()方法,做下一步的处理。
  • 如果没有之前五种情况的任意一种,则可以执行释放操作,C函数的 free()

object_dispose() 调用流程。

id object_dispose(id obj)
{
    if (!obj) return nil;
    objc_destructInstance(obj);    
    free(obj);
    return nil;
}

  • 直接调用 objc_destructInstance()
  • 之后调用 C函数的 free()
  • 执行完毕

objc_destructInstance() 调用流程

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;
}
  • 先判断 hasCxxDtor,如果有 C++ 的相关内容,要调用 object_cxxDestruct() ,销毁 C++ 相关的内容。
  • 再判断 hasAssocitatedObjects,如果有的话,要调用 object_remove_associations(),销毁关联对象的一系列操作。
  • 然后调用 clearDeallocating()
  • 执行完毕。

clearDeallocating() 调用流程。

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();
}
  • 先执行 sideTable_clearDellocating()
  • 再执行 weak_clear_no_lock,在这一步骤中,会将指向该对象的弱引用指针置为 nil
  • 接下来执行 table.refcnts.eraser(),从引用计数表中擦除该对象的引用计数。
  • 至此为止,Dealloc 的执行流程结束。
  1. MRC下如何重写属性的 SetterGetter?
重写setter
-(void)setBrand:(NSString *)brand{
//如果实例变量指向的地址和参数指向的地址不同
    if (_brand != brand)
    {
        //将实例变量的引用计数减一
        [_brand release];
       //将参数变量的引用计数加一,并赋值给实例变量
        _brand = [brand retain];
    }
}
重写getter
-(NSString *)brand{
    //将实例变量的引用计数加1后,添加自动减1
    //作用,保证调用getter方法取值时可以取到值的同时在完全不需要使用后释放
    return [[_brand retain] autorelease];
}
重写dealloc
//MRC下 手动释放内存 可重写dealloc但不要调用dealloc  会崩溃
-(void)dealloc{
    [_string release];
    //必须最后调用super dealloc
    [super  dealloc];
}
  1. 在 Obj-C 中,如何检测内存泄漏?你知道哪些方式?
  • Memory Leaks
  • Allocations
  • Analyse
  • Debug Memory Graph
  • MLeaksFinder

泄露的内存主要有以下两种:

  • Leak Memory 这种是忘记 Release 操作所泄露的内存。
  • Abandon Memory 这种是循环引用,无法释放掉的内存。

前四种都比较麻烦,需要不断地调试运行,第五种是腾讯阅读团队出品,效果好一些

  • MLeaksFinder:精准 iOS 内存泄露检测工具
  • MLeaksFinder 新特性

https://www.xamrdz.com/web/2rv1994696.html

相关文章: