内存管理相关

内存布局

① 栈区stack:方法调用会在栈区展开;
② 堆区heap:通过alloc分配的对象,copy后的block,都是在堆区;
③ bss:为初始化的全局变量
④ data:已经初始化的全局变量
⑤ text:程序的代码段加载到内存中时,都是在text段中的

内存布局图示

内存管理方案

系统针对不同场景提供不同的内存管理方案
① TaggedPointer:对于一些小对象如NSNumber等采用TaggedPointer管理方案;(深入理解Tagged Pointer)
② Nonpointer_isa:64位架构(arm64)下应用采用NONPOINTER_ISDA内存管理方案。在64位架构下,ISA指针占64bit位,实际上有30-40位就够用了,剩下的位数就浪费了,Apple针对这种情况,为了提高内存的利用率,在ISA当中剩余的位中存放了一些内存管理相关的内容,所以这个叫做非指针型ISA(Nonpointer_isa);
③ 散列表:是一个复杂的数据结构,其中包含了弱引用表和引用计数表

内存数据结构

  1. 散列表(side Tables()结构)
  • side Tables()
    实际是一张hash表,能通过hash算法快速查找某一个Side Table
    side Tables()结构图示

    为什么不是一个SideTable而是一个SideTables?
    >为了提高访问效率采用分离锁的方式,将对象分配到不同的Side Table中,这样对多个Side Table中的对象访问可以同步访问,如果只有一个Side Table,只能进行串行访问,影响效率
  • Side Table:
    spinlock_t涉及的内容是一些多线程和资源竞争
    side Table结构图示

    ① 自旋锁spinlock_t:是"忙等"的锁,适用于轻量访问;
    ② 引用计数表RefcountMap:是一个hash表,将指针通过hash算法得到一个unsign long size_tsize_t的第一个表示是否有弱引用指针,第二位表示是否在执行dealloc操作,所以size_t需要
    右移两位才能得到正确的引用计数值
    size_t结构

    ③ 弱引用表weak_table_t:是一个hash表
    weak_table_t

ARC&MRC

  1. MRC手动引用计数:

retain、release、retainCount、autorelease

  1. ARC 自动引用计数:

① ARC 是LLVM编译器(自动添加retain、release等代码)与Runtime(weak表是通过运行时维护的,如weak对象被释放时自动设置为nil)协作的结果;
在 runtime 源码的 objc-internal.h 文件中声明了一些内存管理的函数,代码经由编译器编译会添加这些函数,从而实现引用计数的管理,包括 objc_alloc(Class cls)、objc_retain(id obj)、void objc_release(id obj) 等等,所以可以说 ARC 由编译器和 runtime 协作完成
② ARC 中是禁止调用MRC中的独有方法;
③ ARC 中新增weak、strong关键字

引用计数

alloc:经过一系列函数封装和调用,最终调用了c函数calloc,此时并没有设置引用计数为1;
return:我们在return操作时,系统时如何查找对象的引用计数

   SideTable& table = SideTables()[this];//根据对象通过hash查找到对应的SideTable
   size_t& refcntStorage = table.refcnts[this];//然后通过hash在refcnts引用计数表中查找引用计数
   refcbtStorage += SIDE_TABLE_RC_ONE;

release

 SideTable& table = SideTables()[this];
 RefcountMap::iterator it = table.refcnts.find(this);
 it->second -= SIDE_TABLE_RC_ONE;

retainCount:只通过alloc创建的对象,调用retainCount获取的值为1;

 SideTable& table = SideTables()[this];
 size_t refcnt_result = 1;
 RefcountMap::iterator it = table.refcnts.find(this);
 refcont_result += it->second >> SIDE_TABLE_RC_ONE;

dealloc
非指针型ISANonpointer_isa,弱引用weakly_referenced(如果有需要对弱引用对象进行清理),关联对象assoc,C++、ARC(cxx_dtor),引用计数表sidetable_rc(是否使用了引用计数表管理引用计数)

dealloc工作流程

rootDealloc实现相关

inline void
objc_object::rootDealloc()
{
   assert(!UseGC);
   if (isTaggedPointer()) return;

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

object_dispose实现相关

id 
object_dispose(id obj)
{
   if (!obj) return nil;
   objc_destructInstance(obj);
#if SUPPORT_GC
   if (UseGC) {
       auto_zone_retain(gc_zone, obj); // gc free expects rc==1
   }
#endif
   free(obj);
   return nil;
}

** objc_destructInstance**实现相关

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

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

弱引用

   id __weak obj1 = obj;
   //经过编译后
   id obj1;
   objc_initWeak(&obj1, obj);

weak变量被废弃后为什么会被置为nil:当一个对象被dealloc后,dealloc会调用当前对象的弱引用清除相关函数weak_clear_no_lock(),在weak_clear_no_lock()内部实现中会根据当前函数指针查找弱引用表,把当前对象中的弱引用都取出来,然后分别置为nil

自动释放池

   //编译器会将@autoreleasepool{}改写为
   void *ctx = objc_autoreleasePoolPush();
   {}中的代码
   objc_autoreleasePoolPop(ctx);

自动释放池的结构
① 是以栈为节点通过双向链表的形式组合而成的数据结构。
② 是和线程一一对应的。

class AutoreleasePoolPage{
   ...
   id *next;//指向栈当中下一个可填充的位置
   AutoreleasePoolPage * const parent;//父节点
   AutoreleasePoolPage *child;//孩子节点
   pthread_t const thread;//线程相关
   ...  
}

在当前runloop将要结束的时候,调用AutoreleasePoolPage::pop()。
autoreleasePool的实现原理:以栈为节点通过双向链表的形式组合而成的一个数据结构。
autoreleasePool为什么可以多层嵌套使用:多层嵌套就是在栈中多次插入AutoreleasePoolPage(哨兵)对象。
在什么样的场景需要手动创建autoreleasePool:在进行内存消耗较大的操作时,如在for循环中alloc图片数据等需要手动插入autoreleasePool

循环引用

分类

@interface ClassA : NSObject
@property (nonatomic, strong) id *obj;
@end
@interface ClassB : NSObject
@property (nonatomic, strong) id *obj;
@end

自循环引用:自身持有自身

  ClassA *a = [ClassA alloc] init];
  a.obj = a;

互相循环引用

 ClassA *a = [ClassA alloc] init];
 ClassB *b = [ClassB alloc] init];
 a.obj = b.obj;
 b.obj = a.obj;

多循环引用
即大环引用

出现循环引用场景:代理、block、NSTimer、大环引用
如何破除循环引用:避免产生循环引用(weak,strong)、在合适的时机手动断环
__weak, __block,
__block的使用问题
MRC 环境下,block 截获外部用 __block 修饰的变量,不会增加对象的引用计数。
ARC 环境下,block 截获外部用 __block 修饰的变量,会增加对象的引用计数,无法避免循环引用,需要手动解环。
所以,在 MRC 环境下,可以通过 __block 来打破循环引用,在 ARC 环境下,则需要用 __weak 来打破循环引用。
NSTimer的循环引用问题
通过中间件(iOS中解决NSTimer循环引用问题)
iOS10中,定时器的API新增了block方法,可以使用新方法避免循环引用问题

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

总结

什么是ARC
为什么weak指针指向的对象在废弃之后会被自动置为nil
苹果是如何实现autoreleasePool的(原理)
什么是循环引用,你遇到过哪些循环引用,是怎么解决的

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