内存管理相关

内存布局

① 栈区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的(原理)
什么是循环引用,你遇到过哪些循环引用,是怎么解决的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,248评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,681评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,443评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,475评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,458评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,185评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,451评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,112评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,609评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,083评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,163评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,803评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,357评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,357评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,590评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,636评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,925评论 2 344

推荐阅读更多精彩内容

  • CADisplayLink、NSTimer 使用注意 CADisplayLink 和 NSTimer 会对 tar...
    valentizx阅读 726评论 1 3
  • 1. 内存布局 stack:栈区,方法调用 heap:堆区,通过alloc等分配的对象 bss:未初始化的全局变量...
    X勒个F阅读 194评论 0 0
  • 1.内存布局 stack: 方法调用 heap: 通过alloc等分配的对象 bss: 未初始化的全局变量 dat...
    细雨菲菲v阅读 224评论 0 0
  • 本文主讲内存管理相关面试问题,包括内存布局、内存管理方案、数据结构、ARC&MRC、引用计数管理、弱引用管理、自动...
    骑着毛驴走起来阅读 1,062评论 0 1
  • 内存布局 stack(栈区): 方法调用 heap(堆区):通过alloc等分配的对象 bss:未初始化的全局变量...
    Jimmy_L_Wang阅读 611评论 1 3