RunLoop与AutoReleasepool的关系学习

2020-08-06  本文已影响0人  携YOU手同行

概念的陈述

autoReleasepool是我们iOS开发项目中及其重要的一个内存管理机制,自从我们是用ARC后,我们再也不用开启一个内容分配给某个对象,我们只管做我们开发应该做的事情,内存的分配和释放都交给autoReleasepool去处理,这样既方便也省事,所以autoReleasepool的概念就不多叙述了,自动内存释放池,相信只要做iOS开发的都能明白其作用的重要性。

AutoReleasepool的内部结构

 magic_t const magic;                      magic_t const magic; 
  id *next;                                 id *next;  
 .....                                      NSString *string49;
 NSString *...;                             NSString *string50;
 NSString *...;                             pthread_t const thread;
 NSString *string3;                         ......       
 NSString *string2;                         ......
 NSString *string1;
  pthread_t const thread;
  AutoreleasePoolPage * const 
  AutoreleasePoolPage *child;
  uint32_t const depth;
  uint32_t hiwat;
  PAGE_MAX_SIZE;
  COUNT;
  POOL_BOUNDARY;

  Page 1   //第一页                               page2//第二页           ...

1,AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组成
2,AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址

Runloop与AutoReleasepool的关系

  NSLog(@"相关的关系 %@",[NSRunLoop currentRunLoop]);

当我们在程序中用日志打印相关数据的时候,显示的日志是

2020-07-30 10:38:49.003092+0800 VVeboTableViewDemo[1522:62613] 相关的关系 <CFRunLoop 0x600001ce0300 [0x7fff8062d750]>{wakeup port = 0x1903, stopped = false, ignoreWakeUps = false, 
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600002eba1f0 [0x7fff8062d750]>{type = mutable set, count = 2,
entries =>
    0 : <CFString 0x7fff869c52a0 [0x7fff8062d750]>{contents = "UITrackingRunLoopMode"}
    2 : <CFString 0x7fff80640a20 [0x7fff8062d750]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x600002eba370 [0x7fff8062d750]>{type = mutable set, count = 11,
entries =>
    0 : <CFRunLoopSource 0x6000015e8000 [0x7fff8062d750]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff38c3a9b2)}}
    1 : <CFRunLoopSource 0x6000015f00c0 [0x7fff8062d750]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2e03, callout = PurpleEventCallback (0x7fff38c3a9be)}}
    2 : <CFRunLoopSource 0x6000015fc000 [0x7fff8062d750]>{si

但是相关控制台打印的数据是

0 : <CFRunLoopSource 0x6000015e8000 [0x7fff8062d750]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x7fff38c3a9b2)}}
    1 : <CFRunLoopSource 0x6000015f00c0 [0x7fff8062d750]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2e03, callout = PurpleEventCallback (0x7fff38c3a9be)}}
    2 : <CFRunLoopSource 0x6000015fc000 [0x7fff8062d750]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x600001bf84e0, callout = __handleEventQueue (0x7fff493c2e6e)}}
    3 : <CFRunLoopObserver 0x6000011fc1e0 [0x7fff8062d750]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4931eaa4), context = <CFArray 0x600002edf570 [0x7fff8062d750]>{type = mutable-small, count = 1, values = (
    0 : <0x7fbfa580d048>
)}}
    4 : <CFRunLoopObserver 0x6000011fc000 [0x7fff8062d750]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff4934fa4f), context = <CFRunLoopObserver context 0x7fbfa4c052c0>}
    5 : <CFRunLoopObserver 0x6000011f80a0 [0x7fff8062d750]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff48eaa99e), context = <CFRunLoopObserver context 0x600000bf8e00>}
    6 : <CFRunLoopSource 0x6000015fc0c0 [0x7fff8062d750]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600002ea8db0, callout = __handleHIDEventFetcherDrain (0x7fff493c2edd)}}
    7 : <CFRunLoopSource 0x6000015e0000 [0x7fff8062d750]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000004e0540, callout = FBSSerialQueueRunLoopSourceHandler (0x7fff36d82bd5)}}
    8 : <CFRunLoopObserver 0x6000011fc0a0 [0x7fff8062d750]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff4934fab8), context = <CFRunLoopObserver context 0x7fbfa4c052c0>}
    9 : <CFRunLoopObserver 0x6000011ec320 [0x7fff8062d750]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b477daa), context = <CFRunLoopObserver context 0x0>}
    12 : <CFRunLoopObserver 0x6000011fc140 [0x7fff8062d750]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4931eaa4), context = <CFArray 0x600002edf570 [0x7fff8062d750]>{type = mutable-small, count = 1, values = (
    0 : <0x7fbfa580d048>
)}}

第一个监听的是activities 是 NSRunLoopEntry状态,说明当runloop进入entry状态的时候,会调用_wrapRunLoopWithAutoreleasePoolHandler
,其内部会调用_objc_autoreleasePoolPush()创建自动释放池。
第二个监听的activities是 NSRunLoopBeforeWaiting 和NSRunLoopExit,BeforeWaiting 其回调方法_wrapRunLoopWithAutoreleasePoolHandler内部会调用先调用pop操作,然后再push 创建一个新的自动释放池。Exit会调用pop操作。

autoreleasepool的执行顺序就是Entry-->push ---> BeforeWaiting--->pop-->push -->Exit-->pop,按照这样的顺便,保证了,每一次push都对应一个pop。autoreleasepool释放操作在每一次runloop 的BeforeWaiting和exit的时候执行的

AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置
一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
所以,若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时内存如下图:

图中的情况,这一页再加入一个autorelease对象就要满了(也就是next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。

所以,向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置。

所以runloop 和AutoReleasepool是通过线程的方式一一对应的

什么情况下需要创建自动释放池?

其实自动释放池存在的意义是为了延迟释放一些对象,延迟向对象发送release消息。在实际的开发中,有两种情况是需要手动创建自动释放池的。

1、在多线程中,因为子线程中可能会使用便利构造器等方法来创建对象(类方法),那么这些对象的释放只能放在自动释放池中,此时需要在子线程中添加自动释放池。

使用便利构造器等方法来创建对象是autorelease的对象,需要制动释放池才能释放

主线程已经添加过自动释放池,在main函数里面。

2、就是如果一段代码里面(比如for循环)大量使用便利构造器创建对象,也需要手动添加自动释放池。

什么是便利构造器?

便利构造器在初始化方法的基础上前进了一⼩小步。封装了对象创建过程。

便利构造器是“+”方法,返回本类型的实例,方法名以类名开头,其实一些类中的静态方法也是便利构造器。

可以有0到多个参数。内部实现:封装了alloc和初始化方法。使用起来更加简洁。

runloop和 autorelease pool

先提出一个问题,在Iphone项目中,大家会看到一个默认的Autorelease pool,程序开始时创建,程序退出时销毁,按照对Autorelease的理解,岂不是所有autorelease pool里的对象在程序退出时才release, 这样跟内存泄露有什么区别?结果是,对于每一个Runloop, 系统会隐式创建一个Autorelease pool,这样所有的release pool会构成一个象CallStack一样的一个栈式结构,在每一个Runloop结束时,当前栈顶的Autorelease pool会被销毁,这样这个pool里的每个Object会被release。

那什么是一个runloop?一个UI事件,一个timer,一个系统delegate都称之为runloop(不是NSRunloop),runloop实际上是从接收消息,然后处理完消息的一个完整过程。

为了更加形象说明auto release pool机制,下面举例:

NSString* str1是assign。

UI事件:UIButton的target-action机制,在action中创建一个autorelease的UILabel对象,并赋值,在action中打印出值,action执行完毕,这个时候runloop结束,autorelease pool被释放,label也被释放,所以再调用这个对象的值时,出现bad_exec_access。

上一篇下一篇

猜你喜欢

热点阅读