iOS课程观看笔记(四)---内存管理

本文深入探讨了iOS系统的内存管理机制,包括内存布局、SideTables的实现原理、MRC与ARC的区别,以及引用计数、弱引用和自动释放池的工作机制。通过分析,读者将理解对象的生命周期管理、循环引用的解决策略以及自动释放池的内部结构。

在这里插入图片描述

内存布局

在这里插入图片描述
stack:栈包括:方法调用
heap:堆,通过alloc等分配的对象
bss:未初始化的全局变量以及静态变量等
data:已初始化的全局变量以及静态变量等
text:程序代码


内存管理方案

问:iOS系统怎样对内存进行管理的?

根据不同的场景,提供不同的内存管理方案,大致包括三种:

  • TaggedPointer:NSNumber小对象
  • NONPOINTER_ISA:64位中某些字段存储的是其他信息,不全是指针
  • 散列表:引用计数表、weak弱引用表(平时说的这种)

NONPOINTER_ISA

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

散列表方式

在这里插入图片描述
在这里插入图片描述

struct SideTable 
{
    spinlock_t slock;// 保证原子操作的自旋锁
    RefcountMap refcnts;//引用计数器存储地,是一个哈希map表
    weak_table_t weak_table;//弱引用表,也是哈希map存储
}
问:SideTables存储在哪?SideTable存储在哪?

在源码中,全局搜索SideTables
在这里插入图片描述
注释里面有一个关于initialize SideTables

We cannot use a C++ static initializer to initialize SideTables because libc calls us before our C++ initializers run. We also don't want a global pointer to this struct because of the extra indirection.
Do it the hard way.


我们不能使用C++静态初始化器初始化SideTables,因为libc在我们的C++初始化器运行之前调用我们。
我们也不希望用一个全局的指针指向这个结构体,因为指针是间接访问。

也就是,我们不能用C++静态初始化SideTables
也没有用一个指针指向这个结构体

static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

该类型是一个函数模板,模板类型是StripedMap

StripedMap头部有一个注释:

StripedMap< T > is a map of void* -> T

StripedMap是一个以void*为key,T为value的map

也就是SideTables是一个全局的hash表

参考:
Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t

这是个大佬,可以多学习

问:为什么不是一个SideTable,而是多个?

在这里插入图片描述

如果只有一个SideTable,那么所有的对象都在一个表里面
当对某一个对象进行操作的时候,为了安全,加锁,其他对象就没法操作了
因此,存在效率问题

因此,为了改变这种情况,系统运用了分离锁的技术

在这里插入图片描述
将一张表分成8张不同的表。
如果对象A在表A,对象B在表B,则可以同时对对象AB操作

问:如何实现快速分流?

题目解析:指的是,通过一个对象的指针,如何快速的定位到其所属哪一张SideTable表

在这里插入图片描述


SideTable的数据结构

1spinlock_t slock;// 保证原子操作的自旋锁
2RefcountMap refcnts;//引用计数器存储地,是一个哈希map表
3weak_table_t weak_table;//弱引用表,也是哈希map存储

spinlock_t

其是一种自旋锁,也就是处于“忙等”状态
适用于轻量访问

RefcountMap引用计数表

在这里插入图片描述

其是一个哈希map表
使用哈希表,插入查找都是通过同一个函数,效率高

在这里插入图片描述
上图表面该size_t里面存储的内容(与NONPOINTER_ISA的存储内容无关)

以对象的地址为key,size_t为value
size_t向右偏移两位,就是引用计数器的个数

weak_table_t弱引用表

在这里插入图片描述

/**
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    weak_entry_t *weak_entries;
    size_t    num_entries;
    uintptr_t mask;
    uintptr_t max_hash_displacement;
};

从注释可以看出:
key是Stores object ids,存储对象的地址
value是weak_entry_t

在特定语义环境下
int *a;是一个int 型数组。数组名为a,数组元素类型是int
类似的,
weak_entry_t *weak_entries;是一个结构体数组
数组名是weak_entries,数组元素类型是weak_entry_t
且weak_entry_t是一个结构体

一个对象,可能有多个weak对象修饰,是一对多的关系

__weak NSObject *object = [[NSObject alloc] init];
 __weak NSObject *object1 = object;
 __weak NSObject *object2 = object;

key是[[NSObject alloc] init]的地址,也是object存储的内容
value是weak_entry_t
weak_entry_t里面存储有object、object1、object2的地址

题外话

YZPerson *person = [[YZPerson alloc] init];
一般我们说的person对象,其实指的是[YZPerson alloc] init],也就是person指针指向的对象
因为,YZPerson继承NSObject,而NSObject其实是一个结构体
也就是person是一个结构体类型的指针
也就是person里面存储的内容是一个结构体类型的地址
person是一个指针变量,当然不是一个对象
对象里面要有isa指针的,person里面是一个地址值,没有isa指针
[[YZPerson alloc] init]里面才是建立起来的真正的对象
只是将新建的对象地址,赋值给person指针变量
也就是,我们一般说的person对象,其实指的是等号后面的内容


MRC和ARC

MRC

手动引用计数
alloc、retain、release、retainCount、autorelease、dealloc

其中:在 ARC 下,retain、release、retainCount、autorelease 这四个方法均被禁止显式调用。如果写了,编译器直接报错。

Dealloc 的特殊性:

MRC: 需要显式调用 [super dealloc]。
ARC: 可以重写 dealloc 方法来释放非 OC 资源(如移除通知、释放 C 指针),但禁止在内部调用 [super dealloc](编译器会自动帮你调)。

Autorelease 的替代方案:

虽然 ARC 禁止调用 [obj autorelease] 方法,但 autorelease 机制依然存在。它是通过 @autoreleasepool 代码块、__autoreleasing 修饰符以及编译器的命名规则分析(Naming Conventions)来隐式实现的。

MRC中有autorelease,这个需要注意下

ARC

ARC,自动引用计数
ARC是编译器LLVM和Runtime协作的结果:
LLVM (编译期): 负责代码静态分析,在合适的地方插入 objc_retain、objc_release。

Runtime (运行期): 负责处理 Weak(置 nil 操作)以及 Autorelease 的特殊优化

ARC禁止调用retain、release、retainCount、dealloc
可以重写dealloc,但是禁止使用[super dealloc]
ARC新增weak、strong属性关键字


引用计数管理

alloc、retain、release、retainCount、dealloc的方法实现

alloc的实现

alloc的实现比较简单,最终调用了C函数的calloc
此时,并没有设置引用计数器为1。

大致过程:

+ (id)alloc {
    return _objc_rootAlloc(self);
}

进入_objc_rootAlloc(self)
id
_objc_rootAlloc(Class cls)
{
    return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

进入callAlloc
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());//C语言的calloc函数
if (!obj) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;

obj->initInstanceIsa(cls, dtor);里面有
initIsa(cls, true, hasCxxDtor);

进入initIsa
inline void 
objc_object::initIsa(Class cls, bool indexed, bool hasCxxDtor) 
{ 
    assert(!isTaggedPointer()); 
    
    if (!indexed) {
        isa.cls = cls;
    } else {
        assert(!DisableIndexedIsa);
        isa.bits = ISA_MAGIC_VALUE;
        // isa.magic is part of ISA_MAGIC_VALUE
        // isa.indexed is part of ISA_MAGIC_VALUE
        isa.has_cxx_dtor = hasCxxDtor;
        isa.shiftcls = (uintptr_t)cls >> 3;
    }
}

完成
在里面没有发现关于sidatable表的操作

这个有点粗暴了,一直以来认为的alloc会增加引用计数器,原来并没有

retain的实现

在这里插入图片描述

- (id)retain {
    return _objc_rootRetain(self);
}

进入
NEVER_INLINE id
_objc_rootRetain(id obj)
{
    ASSERT(obj);

    return obj->rootRetain();
}

进入
inline id 
objc_object::rootRetain()
{
    if (isTaggedPointer()) return (id)this;
    return sidetable_retain();
}

进入
id
objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    ASSERT(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];
    
    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
        //#define SIDE_TABLE_RC_ONE            (1UL<<2)左移两位,是4(实际反应出来是1)
    }
    table.unlock();

    return (id)this;
}

可以看到图片中的三句代码:
SideTable& table = SideTables()[this];//找到所属SideTable表
size_t& refcntStorage = table.refcnts[this];//找到现在的引用计数器个数
refcntStorage += SIDE_TABLE_RC_ONE;//引用计数器+1

问:在进行retain操作的时候,系统如何查找引用计数?

从SideTables里通过当前对象指针,找到对应的SideTable表
在SideTable表中,通过当前对象指针,找到对应的size_t
反应出来,就是引用计数器+1
也就是通过两次hash查找,找到引用计数器

release的实现

在这里插入图片描述

- (oneway void)release {
    ((id)self)->rootRelease();
}

进入
ALWAYS_INLINE bool 
objc_object::rootRelease()
{
    return rootRelease(true, false);
}

进入
ALWAYS_INLINE bool 
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
	......
	return sidetable_release(performDealloc);
}

进入sidetable_release
uintptr_t 
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.indexed);
#endif
    SideTable& table = SideTables()[this];

    bool do_dealloc = false;

    if (table.trylock()) {
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it == table.refcnts.end()) {
            do_dealloc = true;
            table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
        } else if (it->second < SIDE_TABLE_DEALLOCATING) {
            // SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
            do_dealloc = true;
            it->second |= SIDE_TABLE_DEALLOCATING;
        } else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
            it->second -= SIDE_TABLE_RC_ONE;
        }
        table.unlock();
        if (do_dealloc  &&  performDealloc) {
            ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
        }
        return do_dealloc;
    }

    return sidetable_release_slow(table, performDealloc);
}

可以看到图片中的三句代码:
SideTable& table = SideTables()[this];//找到所属SideTable表
RefcountMap::iterator it = table.refcnts.find(this);//找到现在的引用计数器个数
it->second -= SIDE_TABLE_RC_ONE;//引用计数器-1


retainCount的实现

在这里插入图片描述

- (NSUInteger)retainCount {
    return ((id)self)->rootRetainCount();
}

进入rootRetainCount()
inline uintptr_t 
objc_object::rootRetainCount()
{
    assert(!UseGC);
    if (isTaggedPointer()) return (uintptr_t)this;

    sidetable_lock();
    isa_t bits = LoadExclusive(&isa.bits);
    if (bits.indexed) {
        uintptr_t rc = 1 + bits.extra_rc;
        if (bits.has_sidetable_rc) {
            rc += sidetable_getExtraRC_nolock();
        }
        sidetable_unlock();
        return rc;
    }

    sidetable_unlock();
    return sidetable_retainCount();
}

进入sidetable_retainCount()
uintptr_t
objc_object::sidetable_retainCount()
{
    SideTable& table = SideTables()[this];

    size_t refcnt_result = 1;
    
    table.lock();
    RefcountMap::iterator it = table.refcnts.find(this);
    if (it != table.refcnts.end()) {
        // this is valid for SIDE_TABLE_RC_PINNED too
        refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
    }
    table.unlock();
    return refcnt_result;
}

可以看到图片中的四句代码:
SideTable& table = SideTables()[this];//找到所属SideTable表
size_t refcnt_result = 1;//定义refcnt_result = 1
RefcountMap::iterator it = table.refcnts.find(this);//找到现在的引用计数器个数
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;//引用计数器+1

return refcnt_result;//返回新的引用计数器

问:新创建的对象,alloc其引用计数器为多少?

新创建的对象,引用计数为0
当调用retainCount查看其引用计数个数的时候,引用计数+1,返回的是1

dealloc的实现

在这里插入图片描述

object_dispose()的实现
在这里插入图片描述

objc_destructInstance()的实现
在这里插入图片描述
clearDeallocating()的实现
在这里插入图片描述
从图中可以看出:
dealloc会将弱引用指针置为nil
dealloc会将对象的引用计数表中的引用计数进行擦除

问:我们通过关联对象方法,给某个类添加了一些实例变量,是否有必要在对象的dealloc方法中将关联对象进行移除销毁操作?

不需要,因为dealloc方法中,系统已经帮我们关联对象的移除操作


弱引用管理

在这里插入图片描述
从图中可以看出,
左边:将obj的地址,赋值给id类型的obj1
右边:首先,创建id obj1。然后通过objc_initWeak函数方法,将obj1与obj关联起来
__weak不是指针什么的,只是告诉我们一个方式,也就是
使用__weak,我们使用objc_initWeak()函数
使用strong,我们使用objc_initStrong()函数(如果有的话)

在这里插入图片描述

id
objc_initWeak(id *location, id newObj)
{
    if (!newObj) {
        *location = nil;
        return nil;
    }

    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}


进入storeWeak
static id 
storeWeak(id *location, objc_object *newObj)
{
	if (HaveNew) {
        newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, 
                                                      (id)newObj, location, 
                                                      CrashIfDeallocating);
        // weak_register_no_lock returns nil if weak store should be rejected

        // Set is-weakly-referenced bit in refcount table.
        if (newObj  &&  !newObj->isTaggedPointer()) {
            newObj->setWeaklyReferenced_nolock();
        }

        // Do not set *location anywhere else. That would introduce a race.
        *location = (id)newObj;
    }
}

在这里插入图片描述

若有则通过hash算法找到对应的弱引用表weak_entry_t,则将弱引用插入weak_entry_t表中
若没有弱引用表weak_entry_t,则新建一个弱引用表weak_entry_t,并插入

在这里插入图片描述

问:weak变量,自动变为nil,是如何实现与操作的?
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();
}

在这里插入图片描述

总结: 当一个对象被dealloc后,dealloc内部实现当中会调用 弱引用清除 的相关函数,然后在这个函数的内部,会根据当前对象指针,查找弱引用表,把当前对象相对应的弱引用都拿出来(是一个数组),然后遍历数组中所有的弱引用指针,分别置为nil。


自动释放池

在这里插入图片描述

问:array是在什么时候被释放的(自动释放池与dealloc的关系)

在当次runloop调用将要结束的时候,调用AutoreleasePool::pop()方法,调用array的release方法,由于array指向的对象引用计数器为0,则调用dealloc方法,然后释放

也就是:runloop的自动释放池 --> 对象的引用计数为0 --> dealloc方法 --> 对象真正的释放

注意:
是先release后,引用计数器为0,才调用dealloc方法
而不是调用dealloc方法,去取将引用计数器设置为0

⚠️:以上仅对工厂方法[NSMutableArray array]有用
而对于 [[NSArray alloc] init]无效!!!


不是所有 array 都会等到 RunLoop 结束

在 ARC 下,情况分两种:

情况 A:alloc/init 创建的局部变量(大多数情况)
- (void)test {
    NSArray *array = [[NSArray alloc] init]; 
    // ... 使用 array
} // 函数结束
  • 真相: 这种情况下,array 对象 根本不会 进自动释放池!
  • 释放时机: 编译器非常智能,它分析出 array 的作用域就在这个 {} 内部。所以,它会在 } 之前,直接插入一条 objc_release(array)
  • 结果: array 指向的对象,在函数执行完的那一瞬间(} 处)就直接 dealloc 了,不需要等待 RunLoop 结束。这是 ARC 的性能优化,避免了自动释放池的 overhead。
情况 B:工厂方法创建(且未被优化)
- (void)test {
    NSArray *array = [NSArray array]; // 传统的 autorelease 对象
}
  • 真相: 这种通常会进入 AutoreleasePool。
  • 释放时机: 正如你所说,等待当前 RunLoop 也就是事件循环即将进入休眠(BeforeWaiting)或退出(Exit)时,Pool 执行 pop,对象才释放。

alloc/init 的局部变量不进自动释放池,而是直接 release

这说明你是一位非常严谨的开发者,对技术有“不盲从”的态度,这非常棒!

关于 “alloc/init 的局部变量不进自动释放池,而是直接 release” 这一点,我可以负责任地告诉你:这是完全正确的。这绝不是博客瞎编的,而是基于 Clang 编译器的官方文档 以及 汇编代码的铁证

既然你希望展开讲讲,那我们就抛开博客,直接看 官方规范底层汇编,用“法医”的方式来解剖这个问题。


官方文档怎么说?(The Bible)

这一切的根源在于 Clang 编译器关于 ARC 的文档 (Clang documentation - Objective-C Automatic Reference Counting)。

文档中定义了 Method Families (方法家族) 的概念。ARC 编译器只认方法名,不认人。

  1. Own-returning methods (返回持有权的方法):

    • 凡是以 alloc, new, copy, mutableCopy 开头的方法。
    • 规则: 这种方法返回的对象,调用者(Caller)默认拥有它的所有权(Retain count +1)。
    • ARC 的动作: 既然是你拥有的(+1),你在作用域结束时,ARC 必须负责把你释放掉(插入 release,-1)。不需要别人(自动释放池)帮忙。
  2. Non-own-returning methods (返回非持有权的方法):

    • 例如 [NSArray array], [Person createPerson] 等工厂方法。
    • 规则: 这种方法返回的对象,调用者拥有它。
    • ARC 的动作: 为了保证这个对象返回给你的时候还没死,方法的实现内部必须把它放入 AutoreleasePool(或者使用 TLS 优化延长寿命)。

结论: alloc/init 属于第一类。你自己申请的内存,编译器知道是你申请的,所以编译器会在你用完后直接帮你 release。这就像你自己做饭自己洗碗,不需要雇钟点工(AutoreleasePool)来洗。

深度展开:为什么要这么做?

你可能会问:“为什么要分这么细?统统扔进自动释放池不行吗?”

这是为了 性能 (Performance)

  1. Autorelease 是昂贵的:

    • 进池子: 需要获取当前线程的 Pool,需要加锁(虽然由 Hot Page 优化但仍有开销),需要把指针压栈。
    • 出池子: RunLoop 结束时,需要遍历池子里的几千个对象,挨个发 release 消息。
    • 内存峰值: 对象的生命周期被强行拉长了。明明你在这个函数里用完就不用了,但它非要等到 RunLoop 结束才死,这期间一直占着内存。
  2. Direct Release 是廉价的:

    • 就是一条简单的函数调用 objc_release
    • 对象用完即毁,内存立即回收。

ARC 的设计哲学:
能立马死,绝不拖到等会儿死。
只要编译器能确定这个对象的生命周期(比如局部变量),它就会优先选择直接 release,而不是 autorelease


四、 特殊情况补充(防止被抬杠)

虽然“局部变量 alloc/init 不进池子”是铁律,但有一个特例:

如果有一个 __weak 指针指向它:

- (void)test {
    NSArray *arr = [[NSArray alloc] init];
    __weak NSArray *weakArr = arr; // 引入了 weak
}

在这种极其特殊的情况下,ARC 确实可能会把 arr 放入 AutoreleasePool。
原因: __weak 的实现依赖于对象存在。为了防止对象在 weak 指针还在读取的时候突然销毁导致 Crash(虽然 weak 会置 nil,但有个中间状态),ARC 有时会延长对象的寿命,将其 autorelease

对于你之前那个问题:“array 是在什么时候被释放的?”

最严谨的回答应该是:

  1. 如果是 [[NSArray alloc] init] 创建的局部变量:不进自动释放池,它在当前作用域结束(大括号 })前,被编译器插入的 objc_release 立即释放。
  2. 如果是 [NSArray array] 创建的局部变量:通常会被放入自动释放池,在 RunLoop 休眠或 pop 时释放。(除非触发了 Fast Autorelease 优化且调用方没有 Retain,这属于编译器的极端优化)。

“alloc/init = 立即释放” vs “工厂方法 = 延迟释放” 这个区别,在做内存峰值优化时非常有用。比如在 for 循环里创建大量临时对象,如果用 alloc/init,通常不需要手动加 @autoreleasepool;但如果用工厂方法,就必须加,否则内存会暴涨。


题外话

这个,准确来说,array是在{}结束的就没有了
因为,array是一个局部变量,而且是在栈上的,不需要程序员手动管理,在}时,array被销毁

上面说的array被释放,其实是指的array指针变量指向的对象,也就是=后面的内容。=后面的内容存储在堆上,需要程序员手动管理,也就是autorelease管理的内容对象。

再有,释放array,就是指的释放array指向的内容

在这里插入图片描述
因此,说成,array什么时候释放,指的是array指针变量指向的对象什么时候释放。

问:AutoreleasePool为何可以嵌套使用?

多层嵌套,就是多次插入哨兵对象

问:AutoreleasePool的实现原理是怎样的?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

批量操作是指:每一次的pop,会将{}内部的所有对象,进行一次release操作。

问:什么是自动释放池?
问:自动释放池的实现结构是怎样的?

自动释放池
是以为结点,通过双向链表的形式组合而成
和线程一一对应

在这里插入图片描述

AutoreleasePoolPage

在这里插入图片描述

class AutoreleasePoolPage 
{
	id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
}

在这里插入图片描述

在这里插入图片描述

[obj autorelease]的实现

在这里插入图片描述

源码分析:
经过:

- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

rootAutorelease2()
return AutoreleasePoolPage::autorelease((id)this);
autoreleaseFast(obj)

static inline id *autoreleaseFast(id obj)
    {
        AutoreleasePoolPage *page = hotPage();
        if (page && !page->full()) {//page有值,且没有满
            return page->add(obj);
        } else if (page) {//page有值,但是满了
            return autoreleaseFullPage(obj, page);
        } else {//page为空
            return autoreleaseNoPage(obj);
        }
    }

autoreleaseFullPage(obj, page);的函数方法:

static __attribute__((noinline))
    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) 
            	page = page->child;
            else 
            	page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);
    }

找page->child,有的话拿page->child
没有page->child,则新建page = new AutoreleasePoolPage(page);
最后page->add(obj)并返回

autoreleaseNoPage(obj);的方法函数:

static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // No pool in place.
        assert(!hotPage());

        if (obj != POOL_SENTINEL  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }

        // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);

        // Push an autorelease pool boundary if it wasn't already requested.
        if (obj != POOL_SENTINEL) {
            page->add(POOL_SENTINEL);
        }

        // Push the requested object.
        return page->add(obj);
    }

新建一个page
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
page->add(obj);将内容添加进去

总结:

如果page有值,且没有满,则插入obj
如果page有值,且满了,则查找其子类,子类有调用,没有就新建page,并插入obj
如果page为空,则新建page,并插入obj

在这里插入图片描述
在这里插入图片描述


循环引用

循环引用可以分为三种:
自循环引用
相互循环引用
多循环引用

自循环引用

在这里插入图片描述

YZPerson里面有一个成员变量YZStudent类型的student
YZStudent *student = [[YZPerson alloc] init];

在这里插入图片描述

在这里插入图片描述

一般需要注意的循环引用有:
代理、block、NSTimer、大环引用
我们重点看下NSTimer

问:如何破除循环引用?

避免产生循环引用
在合适的时机手动断环

问:具体的解决方案有哪些?

__weak
__block
__unsafe_unretained

在这里插入图片描述

__block破解循环引用

MRC下,__block修饰对象不会增加其引用计数,避免了循环引用
ARC下,__block修饰的对象会被强引用,无法避免循环引用,需手动解环

在这里插入图片描述

__unsafe_unretained破解循环引用

修饰对象,不会增加其引用计数,避免了循环引用
如果被修饰的对象在某一时机被释放,会产生悬垂指针
因此,尽量不是有__unsafe_unretained

在这里插入图片描述
VC拥有一个对象,对其有一个强指针引用。
对象有一个NSTimer,假如是强引用
NSTimer会对target有一个强引用,也就是NSTimer强引用对象,从而造成循环引用。

那么,能否将对象拥有NSTimer改成弱引用,解除循环引用呢?

在这里插入图片描述

NSTimer分配调用后,其实会加入到当前线程的Runloop中,也就是当前线程的Runloop会对NSTimer有一个强引用。
如果是主线程,那么也就是主线程的Runloop对NSTimer有一个强引用关系,NSTimer又引用了对象。即使VC对对象的强引用消失,对象还是有一个强指针引用,不会被销毁,从而造成内存泄漏。

NSTimer有重复定时器,和非重复定时器。
如果是非重复定时器,一般我们会在定时器的回调方法中,将定时器销毁,并置为nil,这样的话,是可以解除循环引用并且没有内存泄漏。

但是,如果是重复定时器,就不可以在定时器的回调方法中,将定时器销毁,并置为nil

此处讲的使用中间变量解除循环引用的方法,没有MJ那个方法好,此处不表。
MJ里面是尝试对NSTimer对对象的弱引用
此处,是对象对NSTimer的弱引用

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值