runloop 和 autoreleasepool

一、runloop 和 autoreleasepool 之间的关系

在 Objective-C 和 Cocoa 的世界中,RunLoop 和 Autorelease Pool 都是非常重要的概念,且它们在运行时是相互关联的。

### RunLoop:

RunLoop 是一个事件处理循环,用于管理应用程序的事件和消息。每个线程都可以有它自己的 RunLoop,但它并不会自动启动。主线程的 RunLoop 默认已经启动,因为它主要用于处理 UI 更新、定时器事件、输入事件等。

RunLoop 工作的基本原理是循环等待事件(比如输入、定时器事件或者其他类型的事件源),然后分发它们到对应的处理程序。RunLoop 能够在没有事件处理的时候将线程置于睡眠状态,从而帮助应用程序节省资源。

### Autorelease Pool:

Autorelease Pool 是自动引用计数(ARC)环境下管理内存的一种机制。当你将一个对象加入到自动释放池中时,它的释放会被推迟到自动释放池的块结束时。在手动引用计数(MRC)环境中,开发者需要显式地创建和释放 Autorelease Pools,而在 ARC 环境下,这通常是隐式管理的。

### 它们之间的关系:

在 Cocoa 应用程序中,每次通过 RunLoop 的迭代时,都会创建和排空一个或多个 Autorelease Pool。这通常发生在一个迭代的开始处,并在迭代完成后进行清理,允许任何被发送了 `-autorelease` 消息的对象在不需要时得到释放。

主线程的 RunLoop 自动为每个迭代或者事件循环创建了 Autorelease Pool。在辅助线程中,如果你启动了 RunLoop,你必须手动添加 Autorelease Pool,否则在该线程上创建的任何 “autorelease” 对象都不会被释放,从而可能导致内存泄漏。

在下面的伪代码中,你可以看到 Autorelease Pool 是如何与 RunLoop 相结合工作的:

```objective-c
int main(int argc, char * argv[]) {
    @autoreleasepool {
        // 应用程序对象的设置和启动代码...
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

// 在UIApplicationMain内部的简化伪代码:
void UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName) {
    // ...
    
    // 主线程 RunLoop 配置
    while (true) {
        // 这里创建了一个新的 Autorelease Pool
        @autoreleasepool {
            // RunLoop 等待事件
            // RunLoop 事件分发:
            // - 处理 timers
            // - 处理 input sources
            // - 执行被调度的 blocks
            // ...
        }
        // Autorelease Pool 里的对象在这里被释放
    }
}
```

这个结合的好处是能够在应用程序的任何点均衡内存负载。每次 RunLoop 迭代完毕时,相关的 Autorelease objects 都会被发送 `-release` 消息。因此,这种机制保证了内存不会在应用程序的单个 RunLoop 迭代中积聚太多,这对于长时间运行的应用例如 GUI 应用是非常重要的。

总的来说,RunLoop 和 Autorelease Pool 之间的关系是通过 RunLoop 阶段性地管理内存分配和释放来协同工作的。主线程的 RunLoop 被 Cocoa 框架自动管理,而在自定义辅助线程中使用 RunLoop 时必须自己管理 Autorelease Pool。

二、那 Autorelease Pool 和 引用计数表,弱引用表之间有啥关系?

Autorelease Pool、引用计数表和弱引用表是 Objective-C 和 Swift 中内存管理的重要组件。它们在自动引用计数(ARC)环境下工作,来确保对象生命周期的正确管理。下面是它们各自的作用及之间的关系:

### Autorelease Pool:
Autorelease Pool 用来管理那些被标记为 "autorelease" 的对象。当你将对象发送 `autorelease` 消息时,它会被添加到当前的 Autorelease Pool。当 Autorelease Pool 被 "drain"(清空)时,池中的所有对象都会收到一次 `release` 消息。在应用的 RunLoop 的每个周期结束时,主线程的 Autorelease Pool 会被自动清空,而在辅助线程中你需要手动管理它。

### 引用计数表(Reference Count Table):
每个对象都有一个与之关联的引用计数,这是一个整数值,表示当前有多少个所有者持有该对象。在 ARC 环境下,当你将一个对象赋值给一个强引用变量时,编译器会自动插入代码来增加对象的引用计数;当这个强引用变量不再持有对象时,编译器会插入代码来减少对象的引用计数。当引用计数降至0时,对象会被释放。

### 弱引用表(Weak Reference Table):
弱引用不会增加对象的引用计数,因此不会阻止对象被释放。当一个对象被释放时,所有指向它的弱引用都需要被置为 `nil`,以避免悬挂指针。这一过程是由 Runtime 自动完成的,它会在对象释放的时候遍历弱引用表,清除所有的弱引用。

### 它们之间的关系:
- 当一个对象被设置为 "autorelease" 时,意味着将来某个点(通常是 RunLoop 的末尾),对象的引用计数将被减一。`autorelease` 的调用通常由编译器自动插入,例如一些方法返回临时对象时会用到。
- 引用计数表实时地记录着所有通过强引用持有的对象的引用计数。任何时候一个对象没有强引用指向它并且 Autorelease Pool 中也没有它时,这个对象的引用计数为0,并因此被释放。
- 弱引用表维护着所有弱引用的记录,确保当被弱引用的对象释放时,这些弱引用会被及时清零。

在一个对象的生命周期中,它可能会暂时进入 Autorelease Pool,直到 RunLoop 结束并清空该池。而在整个过程中,对象的引用计数会随着强引用和 `autorelease` 调用的增减而变化。同时,任何弱引用也在对象生命周期结束时被清理掉。

结合起来,这些机制确保了对象能够在不再被需求时被正确地释放,避免了内存泄漏,同时又确保了不会过早地释放对象,避免了悬挂指针的出现。

三、在一个对象的生命周期中,它可能会暂时进入 Autorelease Pool,直到 RunLoop 结束并清空该池。而在整个过程中,对象的引用计数会随着强引用和 autorelease 调用的增减而变化。同时,任何弱引用也在对象生命周期结束时被清理掉。这个怎么理解,请通俗易懂的讲解下

让我们通过一个简单的比喻来理解 Autorelease Pool、强引用、弱引用以及它们在对象生命周期中的作用。

### 强引用:朋友的紧握
想象一下,你是一个社交派对上的气球。每个抓住你的人都是一个“强引用”。只要有人紧紧抓住你,你就可以在派对上"存在"(内存中)。每增加一个人抓住你,你在派对上存在的概率就越大;当最后一个抓住你的人放手后,你也就"消失"了(被内存释放)。

### Autorelease Pool:派对结束前的最后挥手
有时候,人们可能不会马上放手。他们会说:“我会在派对快结束前放手。”这就相当于`autoreleasepool`。它是一个允许人们稍后放手的承诺。你暂时安全,因为尽管这个人不再真正抓住你,但是有一个约定,保证你直到派对结束前都不会“消失”。

当派对(RunLoop 迭代)结束时,所有的“我会在派对快结束前放你”的人都会真的放手,这就意味着 Autorelease Pool 被“清空”了。如果没有其他人紧紧抓住你(没有其他强引用),你就会“消失”。

### 弱引用:闲聊的陌生人
再想象,有些人只是随意地拍了拍你,表示打个招呼,但并没有真的抓住你,这就叫做“弱引用”。这些人对你”存在“于派对上并不重要,他们对你的命运不负责任。如果所有紧紧抓住你的人都放手了,同时只剩下那些拍拍你的人,你就会立即“消失”,即使那些闲聊的人还记得你。

现实中,弱引用是指向对象的指针,这些指针不会阻止对象被释放。当对象被释放后,所有的弱引用都会自动清空,这样编程者就不会意外地尝试访问一个已经不存在的对象。

### 结合在一起
所以,就像气球一样:

- 强引用是保证你在派对上存在的紧握。
- Autorelease Pool 是派对结束前最后的承诺,并在结束时履行承诺。
- 弱引用是对你命运不负责任的闲聊拍拍。

对象在代码中也是如此:

- 强引用保持对象在内存中不被释放。
- `autorelease` 把对象放入 Autorelease Pool,在当前的 RunLoop 迭代结束时释放。
- 弱引用在对象释放时自动变为 `nil`,不会阻止对象被释放。

整个内存管理的过程,就是确保我们在必要时保存对象(强引用),在适当时释放对象(autoreleasepool),和避免访问已经消失的对象(弱引用自动置 `nil`)的混合运用。

四、当一个对象被设置为 "autorelease" 时,意味着将来某个点(通常是 RunLoop 的末尾),对象的引用计数将被减一。autorelease 的调用通常由编译器自动插入,例如一些方法返回临时对象时会用到。 “对象的引用计数将被减一” 这个怎么理解,是 autorelease给他减一么? 还是怎么减一的

“对象的引用计数将被减一”这个表达的意思是,当 Autorelease Pool 被清空的时候,所有被标记为 autorelease 的对象会收到一个 release 消息。这个 release 消息是实际导致引用计数减一的操作。

让我们更具体地解释这一过程:

1. 当你调用一个对象的 `-autorelease` 方法时,你是将这个对象添加到当前线程的最近的 Autorelease Pool 中。此时对象的引用计数并没有改变。

2. 随后,当 Autorelease Pool 被"清空"(也就是当 RunLoop 的迭代结束时,或者显式地调用了 `drain` 方法时),Pool 中的每个对象都会收到一个 `-release` 消息。

3. 每收到一次 `-release` 消息,对象的引用计数就会减一。如果某个对象的引用计数减到0,表示没有任何强引用再指向这个对象,操作系统就会释放该对象,回收它占用的内存空间。

因此,可以这样理解:`autorelease` 方法本身并不修改引用计数,它只是延迟了 `release` 消息的发送。这样做通常是为了从一个方法中返回一个对象而不立即放弃对它的所有权,从而允许调用者使用返回的对象。一旦 Autorelease Pool 被清空,所有的延迟 `release` 操作就会被执行,最终减少对象的引用计数。

举一个简单的例子:

```objective-c
NSObject *myObject = [[NSObject alloc] init]; // 引用计数为 1

[myObject autorelease]; // 将 myObject 添加到 Autorelease Pool 但不修改引用计数

// ...一些代码,可能会添加更多对象到 Autorelease Pool...

// 当前 RunLoop 迭代完成时,Autorelease Pool 被清空
// myObject 现在接收到一条 release 消息,引用计数减一变为 0

// 因为引用计数为 0,myObject 被释放
```

在使用了 `autorelease` 的情况下,你不需要(实际上也不能)调用 `release`,因为这会被 RunLoop 自动管理。如果你手动调用 `release`,你会减少过多的引用计数,导致提前释放对象,这可能会引发程序崩溃。

五、那么 短时间创建大量临时变量的时候使用的 @ autorelease{ } 包裹起来的,和上面的Autorelease Pool 有什么区别和关系?

`@autoreleasepool{ }` 块和 Autorelease Pool 是相关联的。当你使用 `@autoreleasepool{ }` 块时,实际上是在创造一个局部的 Autorelease Pool。使用这个局部 Autorelease Pool 可以帮助及时地释放在其中包裹的代码段创建的那些标记为 autorelease 的对象。这种技术特别有用,在内存有限的情况下或者处理大量数据时,可以帮助避免内存峰值和潜在的内存泄漏。

### 没有 `@autoreleasepool{ }` 的情况

在没有显式使用 `@autoreleasepool{ }` 的情况下,所有的 autorelease 对象都会被添加到当前线程的主 Autorelease Pool 中,这个 Pool 会在当前 RunLoop 迭代完成时清空。

这意味着,如果你在一个循环中创建了大量临时对象并且发送了 autorelease 消息,它们不会被立即释放。相反,它们会一直存在于内存中,直到当前的 RunLoop 迭代结束。这可能会导致内存的暂时增长,如果创建了大量对象,那么内存的峰值可能会非常高,有时甚至可能耗尽可用内存。

### 使用 `@autoreleasepool{ }` 的情况

当你在代码中使用 `@autoreleasepool{ }` 时,实际上是在创建一个新的 Autorelease Pool。在 `@autoreleasepool{ }` 块的开始处创建了一个新的 Autorelease Pool 对象,在块的末尾,这个 Autorelease Pool 被清空,其中所有的对象都会收到 `-release` 消息。

这意味着,当你在处理大量数据的循环中使用 `@autoreleasepool{ }` 时,块内创建的所有 autorelease 对象都会在每次迭代的末尾被释放,而不是等到外部的 RunLoop 结束。这样能显著降低内存峰值,并有效管理应用程序的内存占用。

### 实际示例

下面是一个没有 `@autoreleasepool{ }` 的例子,可能会导致内存峰值问题:

```objective-c
for (NSInteger i = 0; i < largeNumber; i++) {
    NSData *data = [[NSData alloc] init]; // 假设这里返回一个大的数据对象
    // 处理数据
    [data autorelease];
}
// 这里所有的 NSData 对象都不会被释放,直到 RunLoop 结束
```

现在,我们使用 `@autoreleasepool{ }` 优化内存管理:

```objective-c
for (NSInteger i = 0; i < largeNumber; i++) {
    @autoreleasepool {
        NSData *data = [[NSData alloc] init];
        // 处理数据
        [data autorelease];
    }
    // 每次循环结束时,当前的 autorelease 对象都会被释放
}
```

在这个优化过的例子中,在每次迭代结束时,对 `data` 的引用计数进行减少操作,及时释放内存。这样可以防止在某次迭代中创建的大量临时对象占用太多的内存。这就是 `@autoreleasepool{ }` 与 Autorelease Pool 之间的区别和关系。

六、这个 Pool 会在当前 RunLoop 迭代完成时清空。,这个“当前 RunLoop 迭代完成” 怎么理解?主线程的runloop是怎么迭代完成的?

在 Cocoa 和 Cocoa Touch 应用程序中,主线程的 RunLoop 是一个负责处理所有用户输入和各种事件的循环。RunLoop 是在应用启动后由系统自动创建和运行的。它不断循环等待事件的到来,处理后再等待新的事件,所以从概念上来说,主线程的 RunLoop 是永远不会"完成"的,因为它会一直运行直到应用程序终止。

然而,当我们谈论到 RunLoop "迭代完成"时,我们实际上是指 RunLoop 的一个单次循环。RunLoop 的每一次迭代会处理一批事件,包括定时器事件、输入事件以及其他类型的源事件。

"当前 RunLoop 迭代完成" 可以这样理解:

1. RunLoop 开始新的迭代。
2. RunLoop 处理到来的事件和消息,可能包括:
   - 用户触摸事件。
   - 定时器超时。
   - 网络事件回调。
   - 消息端口(IPC)事件。
   - 系统提供的其他事件。
3. 在迭代过程中,应用代码会得到执行,界面更新等操作也在此时进行。
4. 在迭代的末尾,若存在 Autorelease Pool,则会清空当前的 Autorelease Pool。这意味着那些被标记为 autorelease 的对象会接收到 release 消息,并且如果其引用计数变为零,则会被销毁。
5. RunLoop 进入睡眠状态,直到下一个事件的到来或者有源需要处理,然后再次开始新的迭代。

在主线程的 RunLoop 中,Autorelease Pool 的清空通常发生在事件处理的末尾。对于需要在屏幕上更新用户界面的应用来说,这通常意味着界面绘制完毕后,就在准备进入睡眠之前,Autorelease Pool 会被清空。

通过上面的机制,Cocoa 和 Cocoa Touch 确保了在每次 RunLoop 迭代的末尾,内存中那些不再需要的 autorelease 对象能够被及时释放,这有助于避免内存的过量使用,确保应用程序运行更加平滑和有效。在编写应用程序时,一般不需要手动管理主线程的 RunLoop 或其 Autorelease Pool,因为这些都是由 Cocoa/Cocoa Touch 框架自动处理的。但如果在后台线程中执行任务,可能需要手动添加 `@autoreleasepool` 块来管理内存,因为后台线程不会自动创建和管理 Autorelease Pool。

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值