
内存布局

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 编译器只认方法名,不认人。
-
Own-returning methods (返回持有权的方法):
- 凡是以
alloc,new,copy,mutableCopy开头的方法。 - 规则: 这种方法返回的对象,调用者(Caller)默认拥有它的所有权(Retain count +1)。
- ARC 的动作: 既然是你拥有的(+1),你在作用域结束时,ARC 必须负责把你释放掉(插入
release,-1)。不需要别人(自动释放池)帮忙。
- 凡是以
-
Non-own-returning methods (返回非持有权的方法):
- 例如
[NSArray array],[Person createPerson]等工厂方法。 - 规则: 这种方法返回的对象,调用者不拥有它。
- ARC 的动作: 为了保证这个对象返回给你的时候还没死,方法的实现内部必须把它放入 AutoreleasePool(或者使用 TLS 优化延长寿命)。
- 例如
结论: alloc/init 属于第一类。你自己申请的内存,编译器知道是你申请的,所以编译器会在你用完后直接帮你 release。这就像你自己做饭自己洗碗,不需要雇钟点工(AutoreleasePool)来洗。
深度展开:为什么要这么做?
你可能会问:“为什么要分这么细?统统扔进自动释放池不行吗?”
这是为了 性能 (Performance)。
-
Autorelease 是昂贵的:
- 进池子: 需要获取当前线程的 Pool,需要加锁(虽然由 Hot Page 优化但仍有开销),需要把指针压栈。
- 出池子: RunLoop 结束时,需要遍历池子里的几千个对象,挨个发
release消息。 - 内存峰值: 对象的生命周期被强行拉长了。明明你在这个函数里用完就不用了,但它非要等到 RunLoop 结束才死,这期间一直占着内存。
-
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 是在什么时候被释放的?”
最严谨的回答应该是:
- 如果是
[[NSArray alloc] init]创建的局部变量: 它不进自动释放池,它在当前作用域结束(大括号})前,被编译器插入的objc_release立即释放。 - 如果是
[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的弱引用
本文深入探讨了iOS系统的内存管理机制,包括内存布局、SideTables的实现原理、MRC与ARC的区别,以及引用计数、弱引用和自动释放池的工作机制。通过分析,读者将理解对象的生命周期管理、循环引用的解决策略以及自动释放池的内部结构。
1895





