经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS底层原理(八)内存管理(下)
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:49:29  对本文有异议

weak指针

我们通常会使用__weak来对变量进行弱引用,被__weak修饰的变量一旦被释放,会自动置为nil

__unsafe_unretained的作用也是将变量变成弱指针,但是不同于__weak的原因是修饰的变量释放后并不会置为nil

weak的实现原理

我们可以在dealloc析构函数的实现中找到关于弱引用的处理

根据调用轨迹dealloc -> _objc_rootDealloc -> rootDealloc -> object_dispose -> objc_destructInstance -> clearDeallocating -> clearDeallocating_slow找到clearDeallocating_slow来分析

  1. NEVER_INLINE void
  2. objc_object::clearDeallocating_slow()
  3. {
  4. ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
  5. SideTable& table = SideTables()[this];
  6. table.lock();
  7. if (isa.weakly_referenced) {
  8. // 清空弱引用表
  9. weak_clear_no_lock(&table.weak_table, (id)this);
  10. }
  11. if (isa.has_sidetable_rc) {
  12. // 清空引用计数
  13. table.refcnts.erase(this);
  14. }
  15. table.unlock();
  16. }

如果有弱引用表,则进一步调用weak_clear_no_lock去清空弱引用表

  1. void
  2. weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
  3. {
  4. // 当前对象的地址值
  5. objc_object *referent = (objc_object *)referent_id;
  6. // 通过地址值找到弱引用表
  7. weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
  8. if (entry == nil) {
  9. /// XXX shouldn't happen, but does with mismatched CF/objc
  10. //printf("XXX no entry for clear deallocating %p\n", referent);
  11. return;
  12. }
  13. // zero out references
  14. weak_referrer_t *referrers;
  15. size_t count;
  16. if (entry->out_of_line()) {
  17. referrers = entry->referrers;
  18. count = TABLE_SIZE(entry);
  19. }
  20. else {
  21. referrers = entry->inline_referrers;
  22. count = WEAK_INLINE_COUNT;
  23. }
  24. for (size_t i = 0; i < count; ++i) {
  25. objc_object **referrer = referrers[i];
  26. if (referrer) {
  27. if (*referrer == referent) {
  28. *referrer = nil;
  29. }
  30. else if (*referrer) {
  31. _objc_inform("__weak variable at %p holds %p instead of %p. "
  32. "This is probably incorrect use of "
  33. "objc_storeWeak() and objc_loadWeak(). "
  34. "Break on objc_weak_error to debug.\n",
  35. referrer, (void*)*referrer, (void*)referent);
  36. objc_weak_error();
  37. }
  38. }
  39. }
  40. // 移除弱引用表
  41. weak_entry_remove(weak_table, entry);
  42. }

其内部会调用weak_entry_for_referent根据对象的地址值作为key,和mask进行按位与运算在散列表中找到对应的弱引用表

  1. static weak_entry_t *
  2. weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
  3. {
  4. ASSERT(referent);
  5. weak_entry_t *weak_entries = weak_table->weak_entries;
  6. if (!weak_entries) return nil;
  7. // 利用地址值(作为key) & mask = 索引
  8. size_t begin = hash_pointer(referent) & weak_table->mask;
  9. size_t index = begin;
  10. size_t hash_displacement = 0;
  11. while (weak_table->weak_entries[index].referent != referent) {
  12. index = (index+1) & weak_table->mask;
  13. if (index == begin) bad_weak_table(weak_table->weak_entries);
  14. hash_displacement++;
  15. if (hash_displacement > weak_table->max_hash_displacement) {
  16. return nil;
  17. }
  18. }
  19. return &weak_table->weak_entries[index];
  20. }

通过源码分析,我们可以得知weak修饰的属性都会存在一个weak_table类型的散列表中,然后以当前对象的地址值为key将所有的弱引用表进行存储;当该对象被释放时,也是同样的步骤从散列表weak_table中查找到弱引用表并移除

总结:

  • 全局共维护一张SideTables表
  • SideTables中包含多个SideTable,可以通过对象地址的哈希算法找到对应的SideTable
  • SideTable对应着多个对象,里面存储着引用计数表弱引用表,需要再对该对象进行一次哈希才能找到其引用计数表弱引用表

SideTable的关系如下图所示

-w810

autorelease

我们在MRC环境下给一个对象加上autorelease,该对象会在被放到自动释放池中自动进行引用计数管理

  1. int main(int argc, const char * argv[]) {
  2. @autoreleasepool {
  3. Person *p1 = [[[Person alloc] init] autorelease];
  4. }
  5. return 0;
  6. }

autorelease到底做了什么呢?

@autoreleasepool的实现原理

下面我们就来分析@autoreleasepool的实现原理

我们先将这段代码转为C++代码来查看

  1. int main(int argc, const char * argv[]) {
  2. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  3. MJPerson *person = ((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((MJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
  4. }
  5. return 0;
  6. }

发现会生成一个__AtAutoreleasePool类型的结构体,其内部会生成构造函数和析构函数

  1. struct __AtAutoreleasePool {
  2. __AtAutoreleasePool() { // 构造函数,在生成结构体变量的时候调用
  3. atautoreleasepoolobj = objc_autoreleasePoolPush();
  4. }
  5. ~__AtAutoreleasePool() { // 析构函数,在结构体销毁的时候调用
  6. objc_autoreleasePoolPop(atautoreleasepoolobj);
  7. }
  8. void * atautoreleasepoolobj;
  9. };

@autoreleasepool的实现过程就是在代码块的开头和结尾分别调用objc_autoreleasePoolPushobjc_autoreleasePoolPop

然后我们在从objc4源码NSObject.mm可以找到对应的实现

  1. void *
  2. objc_autoreleasePoolPush(void)
  3. {
  4. return AutoreleasePoolPage::push();
  5. }
  6. void
  7. objc_autoreleasePoolPop(void *ctxt)
  8. {
  9. AutoreleasePoolPage::pop(ctxt);
  10. }

我们发现两个函数都会调用到AutoreleasePoolPage类型,我们可以看到该类型的定义如下,本质是一个AutoreleasePoolPageData类型的结构体

  1. class AutoreleasePoolPage : private AutoreleasePoolPageData
  2. {
  3. friend struct thread_data_t;
  4. public:
  5. // 每页的大小
  6. static size_t const SIZE =
  7. #if PROTECT_AUTORELEASEPOOL
  8. PAGE_MAX_SIZE; // must be multiple of vm page size
  9. #else
  10. PAGE_MIN_SIZE; // size and alignment, power of 2
  11. #endif
  12. private:
  13. static pthread_key_t const key = AUTORELEASE_POOL_KEY;
  14. static uint8_t const SCRIBBLE = 0xA3; // 0xA3A3A3A3 after releasing
  15. static size_t const COUNT = SIZE / sizeof(id);
  16. static size_t const MAX_FAULTS = 2;
  17. ....
  18. }
  19. // AutoreleasePoolPageData
  20. struct AutoreleasePoolPageData
  21. {
  22. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  23. struct AutoreleasePoolEntry {
  24. uintptr_t ptr: 48;
  25. uintptr_t count: 16;
  26. static const uintptr_t maxCount = 65535; // 2^16 - 1
  27. };
  28. static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
  29. #endif
  30. magic_t const magic;
  31. __unsafe_unretained id *next;
  32. pthread_t const thread;
  33. AutoreleasePoolPage * const parent; // 指向上一个AutoreleasePoolPage的指针(链表中的第一个为nil)
  34. AutoreleasePoolPage *child; // 指向下一个存储AutoreleasePoolPage的指针(链表中的最后一个为nil)
  35. uint32_t const depth;
  36. uint32_t hiwat;
  37. AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
  38. : magic(), next(_next), thread(_thread),
  39. parent(_parent), child(nil),
  40. depth(_depth), hiwat(_hiwat)
  41. {
  42. }
  43. };
总结
  • @autoreleasepool底层会生成一个__AtAutoreleasePool的对象
  • __AtAutoreleasePool内部又会分别生成两个函数objc_autoreleasePoolPushobjc_autoreleasePoolPop,分别在大括号作用域的开始和结尾进行push(入栈)pop(出栈)操作
  • __AtAutoreleasePool的底层都是依靠AutoreleasePoolPage对象来进行操作的
  • AutoreleasePoolPage是一个双向链表的结构,其内部的child会指向下一个AutoreleasePoolPage对象parent会指向上一个AutoreleasePoolPage对象

objc_autoreleasePoolPush的源码分析

我们通过调用轨迹objc_autoreleasePoolPush -> AutoreleasePoolPage::push来分析内部具体做了什么

  1. // 入栈
  2. static inline void *push()
  3. {
  4. id *dest;
  5. if (slowpath(DebugPoolAllocation)) {
  6. // Each autorelease pool starts on a new pool page.
  7. // 创建一个新的page对象,将POOL_BOUNDARY加进去
  8. dest = autoreleaseNewPage(POOL_BOUNDARY);
  9. } else {
  10. // 已有page对象,快速加入POOL_BOUNDARY
  11. dest = autoreleaseFast(POOL_BOUNDARY);
  12. }
  13. ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
  14. return dest;
  15. }

【第一步】如果没有新的page对象,那么会调用autoreleaseNewPage

  1. static __attribute__((noinline))
  2. id *autoreleaseNewPage(id obj)
  3. {
  4. // 获取当前操作页
  5. AutoreleasePoolPage *page = hotPage();
  6. // 将POOL_BOUNDARY加到page中(入栈)
  7. if (page) return autoreleaseFullPage(obj, page);
  8. else return autoreleaseNoPage(obj);
  9. }
  10. // 获取当前操作页
  11. static inline AutoreleasePoolPage *hotPage()
  12. {
  13. // 获取当前页
  14. AutoreleasePoolPage *result = (AutoreleasePoolPage *)
  15. tls_get_direct(key);
  16. // 如果是一个空池,则返回nil,否则,返回当前线程的自动释放池
  17. if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
  18. if (result) result->fastcheck();
  19. return result;
  20. }

autoreleaseNewPage内部又会分别判断有page和没有page的操作

1.有page就调用autoreleaseFullPage将对象压入栈

  1. static __attribute__((noinline))
  2. id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
  3. {
  4. // The hot page is full.
  5. // Step to the next non-full page, adding a new page if necessary.
  6. // Then add the object to that page.
  7. ASSERT(page == hotPage());
  8. ASSERT(page->full() || DebugPoolAllocation);
  9. // 循环遍历当前page是否满了
  10. do {
  11. // 如果子页面存在,则将页面替换为子页面
  12. if (page->child) page = page->child;
  13. // 如果子页面不存在,则新建页面
  14. else page = new AutoreleasePoolPage(page);
  15. } while (page->full());
  16. // 设置为当前操作page
  17. setHotPage(page);
  18. // 压入栈
  19. return page->add(obj);
  20. }
  21. // 设置当前操作页
  22. static inline void setHotPage(AutoreleasePoolPage *page)
  23. {
  24. if (page) page->fastcheck();
  25. tls_set_direct(key, (void *)page);
  26. }
  27. static inline AutoreleasePoolPage *coldPage()
  28. {
  29. AutoreleasePoolPage *result = hotPage();
  30. if (result) {
  31. while (result->parent) {
  32. result = result->parent;
  33. result->fastcheck();
  34. }
  35. }
  36. return result;
  37. }

add里进行真正的压栈操作

  1. id *add(id obj)
  2. {
  3. ASSERT(!full());
  4. unprotect();
  5. id *ret; // 对象存储的位置
  6. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  7. if (!DisableAutoreleaseCoalescing || !DisableAutoreleaseCoalescingLRU) {
  8. if (!DisableAutoreleaseCoalescingLRU) {
  9. if (!empty() && (obj != POOL_BOUNDARY)) {
  10. AutoreleasePoolEntry *topEntry = (AutoreleasePoolEntry *)next - 1;
  11. for (uintptr_t offset = 0; offset < 4; offset++) {
  12. AutoreleasePoolEntry *offsetEntry = topEntry - offset;
  13. if (offsetEntry <= (AutoreleasePoolEntry*)begin() || *(id *)offsetEntry == POOL_BOUNDARY) {
  14. break;
  15. }
  16. if (offsetEntry->ptr == (uintptr_t)obj && offsetEntry->count < AutoreleasePoolEntry::maxCount) {
  17. if (offset > 0) {
  18. AutoreleasePoolEntry found = *offsetEntry;
  19. memmove(offsetEntry, offsetEntry + 1, offset * sizeof(*offsetEntry));
  20. *topEntry = found;
  21. }
  22. topEntry->count++;
  23. ret = (id *)topEntry; // need to reset ret
  24. goto done;
  25. }
  26. }
  27. }
  28. } else {
  29. if (!empty() && (obj != POOL_BOUNDARY)) {
  30. AutoreleasePoolEntry *prevEntry = (AutoreleasePoolEntry *)next - 1;
  31. if (prevEntry->ptr == (uintptr_t)obj && prevEntry->count < AutoreleasePoolEntry::maxCount) {
  32. prevEntry->count++;
  33. ret = (id *)prevEntry; // need to reset ret
  34. goto done;
  35. }
  36. }
  37. }
  38. }
  39. #endif
  40. // 传入对象存储的位置
  41. ret = next; // faster than `return next-1` because of aliasing
  42. // 将obj压栈到next指针位置,然后next进行++,即下一个对象存储的位置
  43. *next++ = obj;
  44. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  45. // Make sure obj fits in the bits available for it
  46. ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);
  47. #endif
  48. done:
  49. protect();
  50. return ret;
  51. }

2.在autoreleaseNewPage内部判断没有page就会去调用autoreleaseNoPage创建新的page,然后在进行压栈操作

  1. static __attribute__((noinline))
  2. id *autoreleaseNoPage(id obj)
  3. {
  4. // "No page" could mean no pool has been pushed
  5. // or an empty placeholder pool has been pushed and has no contents yet
  6. ASSERT(!hotPage());
  7. bool pushExtraBoundary = false;
  8. // 判断是否为空占位符,如果是,则将入栈标识为true
  9. if (haveEmptyPoolPlaceholder()) {
  10. // We are pushing a second pool over the empty placeholder pool
  11. // or pushing the first object into the empty placeholder pool.
  12. // Before doing that, push a pool boundary on behalf of the pool
  13. // that is currently represented by the empty placeholder.
  14. pushExtraBoundary = true;
  15. }
  16. // 如果不是POOL_BOUNDARY,并且没有pool,则报错
  17. else if (obj != POOL_BOUNDARY && DebugMissingPools) {
  18. // We are pushing an object with no pool in place,
  19. // and no-pool debugging was requested by environment.
  20. _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
  21. "autoreleased with no pool in place - "
  22. "just leaking - break on "
  23. "objc_autoreleaseNoPool() to debug",
  24. objc_thread_self(), (void*)obj, object_getClassName(obj));
  25. objc_autoreleaseNoPool(obj);
  26. return nil;
  27. }
  28. // 如果对象是POOL_BOUNDARY,且没有申请自动释放池内存,则设置一个空占位符存储在tls中,其目的是为了节省内存
  29. else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
  30. // We are pushing a pool with no pool in place,
  31. // and alloc-per-pool debugging was not requested.
  32. // Install and return the empty pool placeholder.
  33. return setEmptyPoolPlaceholder();
  34. }
  35. // We are pushing an object or a non-placeholder'd pool.
  36. // Install the first page.
  37. // 初始化第一页
  38. AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
  39. // 设置为当前页
  40. setHotPage(page);
  41. // Push a boundary on behalf of the previously-placeholder'd pool.
  42. // 如果标识为true,则压入栈
  43. if (pushExtraBoundary) {
  44. page->add(POOL_BOUNDARY);
  45. }
  46. // Push the requested object or pool.
  47. return page->add(obj);
  48. }

【第二步】 如果一开始就有page页面,那么直接进入到autoreleaseFast,再分别进行判断

  1. static inline id *autoreleaseFast(id obj)
  2. {
  3. AutoreleasePoolPage *page = hotPage();
  4. if (page && !page->full()) { // 已有page,并且没满
  5. return page->add(obj);
  6. } else if (page) {
  7. // 如果满了,则安排新的page
  8. return autoreleaseFullPage(obj, page);
  9. } else {
  10. // page不存在,新建
  11. return autoreleaseNoPage(obj);
  12. }
  13. }
总结
  • 每一个AutoreleasePoolPage对象都会有一定的存储空间,大概占用4096个字节
  • 每一个AutoreleasePoolPage对象内部的成员变量会占56个字节,然后剩余的空间才用来存储autorelease对象
  • 每一个@autoreleasePool的开始都会先将POOL_BOUNDARY对象压入栈,然后才开始存储autorelease对象,并且push方法会返回POOL_BOUNDARY对象的内存地址
  • 当一个AutoreleasePoolPage对象存满后才会往下一个AutoreleasePoolPage对象里开始存储
  • AutoreleasePoolPage对象里面的beginend分别对应着autorelease对象开始入栈的起始地址和结束地址
  • AutoreleasePoolPage对象里面的next指向下一个能存放autorelease对象地址的区域

上面整个push入栈的过程分析可以用下图来概述

autorelease的源码分析

下面我们来看一下autorelease底层做了什么

  1. // objc_object::autorelease
  2. inline id
  3. objc_object::autorelease()
  4. {
  5. ASSERT(!isTaggedPointer());
  6. if (fastpath(!ISA()->hasCustomRR())) {
  7. return rootAutorelease();
  8. }
  9. return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
  10. }
  11. // objc_object::rootAutorelease
  12. inline id
  13. objc_object::rootAutorelease()
  14. {
  15. // 如果是TaggedPointer就返回
  16. if (isTaggedPointer()) return (id)this;
  17. if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
  18. return rootAutorelease2();
  19. }
  20. // objc_object::rootAutorelease2
  21. __attribute__((noinline,used))
  22. id
  23. objc_object::rootAutorelease2()
  24. {
  25. ASSERT(!isTaggedPointer());
  26. return AutoreleasePoolPage::autorelease((id)this);
  27. }

发现最后还是会调用到AutoreleasePoolPageautorelease

  1. static inline id autorelease(id obj)
  2. {
  3. ASSERT(!obj->isTaggedPointerOrNil());
  4. id *dest __unused = autoreleaseFast(obj);
  5. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  6. ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
  7. #else
  8. ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
  9. #endif
  10. return obj;
  11. }

然后进入到快速压栈autoreleaseFast进行压栈操作,autoreleasepool只会将调用了autorelease的对象压入栈

autoreleaseobjc_autoreleasePush的整体分析如下图所示

objc_autoreleasePoolPop的源码分析

【第一步】我们通过调用轨迹objc_autoreleasePoolPop -> AutoreleasePoolPage::pop来分析内部具体做了什么

  1. static inline void
  2. pop(void *token)
  3. {
  4. AutoreleasePoolPage *page;
  5. id *stop;
  6. // 判断是否为空占位符
  7. if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
  8. // Popping the top-level placeholder pool.
  9. // 获取当前页
  10. page = hotPage();
  11. if (!page) {
  12. // Pool was never used. Clear the placeholder.
  13. // 如果当前页不存在,则清除空占位符
  14. return setHotPage(nil);
  15. }
  16. // Pool was used. Pop its contents normally.
  17. // Pool pages remain allocated for re-use as usual.
  18. // 如果当前页存在,则将当前页设置为coldPage,token设置为coldPage的开始位置
  19. page = coldPage();
  20. token = page->begin();
  21. } else {
  22. // 获取token所在的page
  23. page = pageForPointer(token);
  24. }
  25. stop = (id *)token;
  26. // 判断最后一个位置,是否是POOL_BOUNDARY
  27. if (*stop != POOL_BOUNDARY) {
  28. // 如果不是,即最后一个位置是一个对象
  29. if (stop == page->begin() && !page->parent) {
  30. // Start of coldest page may correctly not be POOL_BOUNDARY:
  31. // 1. top-level pool is popped, leaving the cold page in place
  32. // 2. an object is autoreleased with no pool
  33. // 如果是第一个位置,且没有父节点,什么也不做
  34. } else {
  35. // Error. For bincompat purposes this is not
  36. // fatal in executables built with old SDKs.
  37. // 如果是第一个位置,且有父节点,则出现了混乱
  38. return badPop(token);
  39. }
  40. }
  41. if (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
  42. return popPageDebug(token, page, stop);
  43. }
  44. // 出栈
  45. return popPage<false>(token, page, stop);
  46. }

beginend分别对应着autorelease对象的起始地址和结束地址

  1. // 开始存放autorelease对象的地址:开始地址 + 他本身占用的大小
  2. id * begin() {
  3. return (id *) ((uint8_t *)this+sizeof(*this));
  4. }
  5. // 结束地址:开始地址 + PAGE_MAX_SIZE
  6. id * end() {
  7. return (id *) ((uint8_t *)this+SIZE);
  8. }
  9. // coldPage
  10. static inline AutoreleasePoolPage *coldPage()
  11. {
  12. AutoreleasePoolPage *result = hotPage();
  13. if (result) {
  14. while (result->parent) {
  15. result = result->parent;
  16. result->fastcheck();
  17. }
  18. }
  19. return result;
  20. }

【第二步】然后进入popPage进行出栈操作

  1. template<bool allowDebug>
  2. static void
  3. popPage(void *token, AutoreleasePoolPage *page, id *stop)
  4. {
  5. if (allowDebug && PrintPoolHiwat) printHiwat();
  6. // 出栈当前操作页面对象
  7. page->releaseUntil(stop);
  8. // memory: delete empty children
  9. // 删除空子项
  10. if (allowDebug && DebugPoolAllocation && page->empty()) {
  11. // special case: delete everything during page-per-pool debugging
  12. // 获取当前页面的父节点
  13. AutoreleasePoolPage *parent = page->parent;
  14. //删除将当前页面
  15. page->kill();
  16. // 设置操作页面为父节点页面
  17. setHotPage(parent);
  18. } else if (allowDebug && DebugMissingPools && page->empty() && !page->parent) {
  19. // special case: delete everything for pop(top)
  20. // when debugging missing autorelease pools
  21. page->kill();
  22. setHotPage(nil);
  23. } else if (page->child) {
  24. // hysteresis: keep one empty child if page is more than half full
  25. // 如果页面已满一半以上,则保留一个空子级
  26. if (page->lessThanHalfFull()) {
  27. page->child->kill();
  28. }
  29. else if (page->child->child) {
  30. page->child->child->kill();
  31. }
  32. }
  33. }
  34. // kill
  35. void kill()
  36. {
  37. // Not recursive: we don't want to blow out the stack
  38. // if a thread accumulates a stupendous amount of garbage
  39. AutoreleasePoolPage *page = this;
  40. while (page->child) page = page->child;
  41. AutoreleasePoolPage *deathptr;
  42. do {
  43. deathptr = page;
  44. // 子节点 变成 父节点
  45. page = page->parent;
  46. if (page) {
  47. page->unprotect();
  48. //子节点置空
  49. page->child = nil;
  50. page->protect();
  51. }
  52. delete deathptr;
  53. } while (deathptr != this);
  54. }

内部会调用releaseUntil循环遍历进行pop操作

  1. void releaseUntil(id *stop)
  2. {
  3. // Not recursive: we don't want to blow out the stack
  4. // if a thread accumulates a stupendous amount of garbage
  5. // 循环遍历
  6. // 判断下一个对象是否等于stop,如果不等于,则进入while循环
  7. while (this->next != stop) {
  8. // Restart from hotPage() every time, in case -release
  9. // autoreleased more objects
  10. AutoreleasePoolPage *page = hotPage();
  11. // fixme I think this `while` can be `if`, but I can't prove it
  12. // 如果当前页是空的
  13. while (page->empty()) {
  14. // 将page赋值为父节点页
  15. page = page->parent;
  16. // 并设置当前页为父节点页
  17. setHotPage(page);
  18. }
  19. page->unprotect();
  20. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  21. AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;
  22. // create an obj with the zeroed out top byte and release that
  23. id obj = (id)entry->ptr;
  24. int count = (int)entry->count; // grab these before memset
  25. #else
  26. id obj = *--page->next;
  27. #endif
  28. memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
  29. page->protect();
  30. if (obj != POOL_BOUNDARY) { // 只要不是POOL_BOUNDARY,就进行release
  31. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  32. // release count+1 times since it is count of the additional
  33. // autoreleases beyond the first one
  34. for (int i = 0; i < count + 1; i++) {
  35. objc_release(obj);
  36. }
  37. #else
  38. objc_release(obj);
  39. #endif
  40. }
  41. }
  42. // 设置当前页
  43. setHotPage(this);
  44. #if DEBUG
  45. // we expect any children to be completely empty
  46. for (AutoreleasePoolPage *page = child; page; page = page->child) {
  47. ASSERT(page->empty());
  48. }
  49. #endif
  50. }
总结
  • pop函数会将POOL_BOUNDARY的内存地址传进去
  • autorelease对象end的结束地址开始进行发送release消息,一直找到POOL_BOUNDARY为止
  • 一旦发现当前页已经空了,就会去上一个页面进行pop,并释放当前页面
  • 整个入栈出栈的顺序是采用先进后出,和栈中顺序一样,但不代表着这里说的是真正的栈

上面整个pop出栈的过程分析可以用下图来概述

通过打印分析执行过程

我们可以通过一个私有函数_objc_autoreleasePoolPrint来打印分析整个autorelease的过程

  1. // 声明内部私有函数,可以调用执行
  2. extern void _objc_autoreleasePoolPrint(void);
  3. int main(int argc, const char * argv[]) {
  4. @autoreleasepool { // r1 = push()
  5. Person *p1 = [[[Person alloc] init] autorelease];
  6. Person *p2 = [[[Person alloc] init] autorelease];
  7. @autoreleasepool { // r2 = push()
  8. MJPerson *p3 = [[[Person alloc] init] autorelease];
  9. _objc_autoreleasePoolPrint();
  10. } // pop(r2)
  11. } // pop(r1)
  12. return 0;
  13. }

可以看到打印结果如下

  1. objc[25057]: ##############
  2. objc[25057]: AUTORELEASE POOLS for thread 0x1000e7e00
  3. objc[25057]: 5 releases pending.
  4. objc[25057]: [0x107009000] ................ PAGE (hot) (cold)
  5. objc[25057]: [0x107009038] ################ POOL 0x107009038
  6. objc[25057]: [0x107009040] 0x10060f120 Person
  7. objc[25057]: [0x107009048] 0x100606800 Person
  8. objc[25057]: [0x107009050] ################ POOL 0x107009050
  9. objc[25057]: [0x107009058] 0x100607de0 Person
  10. objc[25057]: ##############

面试题

1.@dynamic和@synthesize两个关键字的含义

在旧版的编译器,加上@synthesize会生成带下划线的成员变量和setter、getter的实现,现在的编译器已经不用加上这个关键字也可以自动实现了

  1. // 成员变量为_age
  2. @synthesize age = _age;
  3. // 不赋值的话,成员变量就是age
  4. @synthesize age

加上@dynamic不会自动生成setter和getter的实现和成员变量

  1. @dynamic age;

所有的声明都是由@property来决定的

2.分别运行下面两段代码,思考能发生什么事,有什么区别

  1. @interface ViewController ()
  2. @property (strong, nonatomic) NSString *name;
  3. @end
  4. @implementation ViewController
  5. - (void)viewDidLoad {
  6. [super viewDidLoad];
  7. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  8. // 第一段
  9. for (int i = 0; i < 1000; i++) {
  10. dispatch_async(queue, ^{
  11. self.name = [NSString stringWithFormat:@"abcdefghijk"];
  12. });
  13. }
  14. // 第二段
  15. for (int i = 0; i < 1000; i++) {
  16. dispatch_async(queue, ^{
  17. self.name = [NSString stringWithFormat:@"abc"];
  18. });
  19. }
  20. }
  21. @end

【第一段代码】

由于给self.name赋值会调用name的settersetter的实现是先释放掉旧的成员变量,然后赋值新的成员变量;又因为是多线程并发调用,所以name被多次释放造成坏内存访问

解决办法:在dispatch_async的回调中给self.name赋值加锁

【第二段代码】

程序不会崩溃。

我们先分别打印两个字符串,从打印类型和内存地址都可以发现第二个字符串是经过了TaggedPointer优化过的,所以不会调用setter,也就不会被多次释放造成崩溃了

  1. NSString *str1 = [NSString stringWithFormat:@"abcdefghijk"];
  2. NSString *str2 = [NSString stringWithFormat:@"abc"];
  3. NSLog(@"%@ %@", [str1 class], [str2 class]);
  4. NSLog(@"%p %p", str1, str2);
  5. // 输出:__NSCFString NSTaggedPointerString
  6. // 0x600000a8d6c0 0x818ff819168b363d

3.ARC都帮我们做了什么

ARCLLVMRuntime相互协作的产物;LLVM会在编译阶段帮我们生成内存管理相关的代码,Runtime又会在运行时进行内存管理的操作

4.局部变量具体是在什么时候进行释放的

  • 如果是不被修饰的局部变量,会在函数内作用域结束进行释放
  • 如果是被@autoreleasePool修饰的,那么会交由自动释放池管理
  • 如果是调用了autorelease,那么会被加到RunLoop中进行管理

看下面这段代码,对象在执行完viewWillAppear后才被释放

  1. - (void)viewDidLoad {
  2. [super viewDidLoad];
  3. Person *person = [[[Person alloc] init] autorelease];
  4. NSLog(@"%s", __func__);
  5. }
  6. - (void)viewWillAppear:(BOOL)animated
  7. {
  8. [super viewWillAppear:animated];
  9. NSLog(@"%s", __func__);
  10. }
  11. - (void)viewDidAppear:(BOOL)animated
  12. {
  13. [super viewDidAppear:animated];
  14. NSLog(@"%s", __func__);
  15. }

Runloop在进入循环时会先执行一次objc_autoreleasePoolPush,然后再进入睡眠之前会执行一次objc_autoreleasePoolPopobjc_autoreleasePoolPush,就这样一直循环;等到程序真正退出时再回执行一次objc_autoreleasePoolPop

由此也可以发现viewDidLoadviewWillAppear是在同一次运行循环中

原文链接:http://www.cnblogs.com/funkyRay/p/ios-di-ceng-yuan-li-ba-nei-cun-guan-li-xia.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号