内存管理:AutoreleasePool

MRC

1.retain/release

1
2
3
4
5
6
7
8
9
@protocol NSObject
//...
- (instancetype)retain OBJC_ARC_UNAVAILABLE;
- (oneway void)release OBJC_ARC_UNAVAILABLE;
- (instancetype)autorelease OBJC_ARC_UNAVAILABLE;
- (NSUInteger)retainCount OBJC_ARC_UNAVAILABLE;

- (struct _NSZone *)zone OBJC_ARC_UNAVAILABLE;
@end

在MRC时代,对象的生命周期是通过引用计数来管理的,我们需要通过频繁地手动添加retain来使引用计数 +1,通过release来使引用计数 -1,当对象的引用计数为 0 时,对象会被销毁。

#示例1:

1
2
3
4
5
6
7
8
- (void)setName:(NSString *)newName
{
if (_name != newName) {
[newName retain]//引用计数+1,强引用新值
[_name release];//引用计数-1,释放旧值
_name = newName;
}
}

这样的管理方式不仅比较繁琐,而且可能会因为疏忽大意而导致内存泄露或过度释放情况的发生。比如示例1中,我们是先通过retain使newName引用计数 +1,再通过release使引用计数 -1。如果这两者顺序颠倒,则就可能出问题。假设newName和_name 的旧值是同一个对象,那么先release旧值就会使旧值的引用计数为0,从而导致对象被提前释放,后面再赋值给_name 就无效。

2.自动释放池

与 release 相比,autorelease算是一种延迟对象释放时间的方式。它需要配合 autorelease pool(自动释放池)使用,我们只需要将对象标记为autorelease,这样对象就会被自动加入到自动释放池中;自动释放池会在合适的时机自动或手动执行drain方法进行销毁;销毁前会向其内部的这些对象发送release消息,从而使对象的引用计数-1;当引用计数为 0 时,对象的内存空间就会被释放。一个对象可以被多次放入到同一个自动释放池内,并且每次放入池内时都会调用一次release方法。

1
NSDictionary *dic = [[[NSDictionary alloc] init] autorelease];

自动释放池在 MRC 和 ARC 中有不同的形式和使用方法:

  • MRC 中的 NSAutoreleasePool
1
2
3
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Code benefitting from a local autorelease pool.
[pool release];
  • ARC 中的 @autoreleasepool{}
1
2
3
@autoreleasepool {
// Code benefitting from a local autorelease pool.
}

在ARC环境下,我们不能直接使用 NSAutoreleasePool 对象,而要使用后者。根据 开发文档 的描述,后者的效率更高。

3.pool的创建

3.1.自动

大部分情况下,系统已经为我们创建了自动释放池,并不需要我们自己创建。那么系统是什么时候帮我们创建的自动释放池呢。。。?这就要结合 runloop 来分析,下面这段内容是应用执行到didFinishLaunchingWithOptions时,通过 po [NSRunLoop currentRunLoop] 打印的当前runloop的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
po [NSRunLoop currentRunLoop]
<CFRunLoop 0x600001cb0100 [0x10bee2b68]>{wakeup port = 0x1f07, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600002ef2910 [0x10bee2b68]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x116ab9be0 [0x10bee2b68]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x10bef8168 [0x10bee2b68]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x600002e81da0 [0x10bee2b68]>{type = mutable set, count = 13,
entries =>
0 : <CFRunLoopSource 0x6000015b5e00 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x110820188)}}
1 : <CFRunLoopSource 0x6000015b0540 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x6000015b5080, callout = __handleEventQueue (0x116270912)}}
2 : <CFRunLoopSource 0x6000015b1f80 [0x10bee2b68]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000004b1680, callout = FBSSerialQueueRunLoopSourceHandler (0x118c99f0b)}}
3 : <CFRunLoopObserver 0x6000011b0c80 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1161aedfc), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}
4 : <CFRunLoopObserver 0x6000011b0be0 [0x10bee2b68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (
0 : <0x7ffe6c009058>
)}}
5 : <CFRunLoopObserver 0x6000011b0b40 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1161aee75), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}
6 : <CFRunLoopObserver 0x6000011b0aa0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (
0 : <0x7ffe6c009058>
)}}
7 : <CFRunLoopSource 0x6000015b0cc0 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x110820194)}}
13 : <CFRunLoopSource 0x6000015b0e40 [0x10bee2b68]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 13583, subsystem = 0x116a53448, context = 0x0}}
14 : <CFRunLoopSource 0x6000015b0480 [0x10bee2b68]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600002e81830, callout = __handleHIDEventFetcherDrain (0x11627091e)}}
15 : <CFRunLoopObserver 0x6000011b05a0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1117046ae), context = <CFRunLoopObserver context 0x0>}
18 : <CFRunLoopSource 0x6000015b4480 [0x10bee2b68]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42499, subsystem = 0x116a654b0, context = 0x6000020acb80}}
21 : <CFRunLoopObserver 0x6000011b4640 [0x10bee2b68]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x115d51473), context = <CFRunLoopObserver context 0x600000bb5a40>}
}
,
modes = <CFBasicHash 0x600002ef2850 [0x10bee2b68]>{type = mutable set, count = 4,
entries =>
2 : <CFRunLoopMode 0x600001bb4270 [0x10bee2b68]>{name = UITrackingRunLoopMode, port set = 0x5103, queue = 0x600000eb5500, source = 0x600000eb5600 (not fired), timer port = 0x5003,
sources0 = <CFBasicHash 0x600002e81e30 [0x10bee2b68]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x6000015b5e00 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x110820188)}}
4 : <CFRunLoopSource 0x6000015b0540 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x6000015b5080, callout = __handleEventQueue (0x116270912)}}
5 : <CFRunLoopSource 0x6000015b1f80 [0x10bee2b68]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000004b1680, callout = FBSSerialQueueRunLoopSourceHandler (0x118c99f0b)}}
6 : <CFRunLoopSource 0x6000015b0480 [0x10bee2b68]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600002e81830, callout = __handleHIDEventFetcherDrain (0x11627091e)}}
}
,
sources1 = <CFBasicHash 0x600002e81e60 [0x10bee2b68]>{type = mutable set, count = 3,
entries =>
0 : <CFRunLoopSource 0x6000015b0e40 [0x10bee2b68]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 13583, subsystem = 0x116a53448, context = 0x0}}
1 : <CFRunLoopSource 0x6000015b4480 [0x10bee2b68]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42499, subsystem = 0x116a654b0, context = 0x6000020acb80}}
2 : <CFRunLoopSource 0x6000015b0cc0 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x110820194)}}
}
,
observers = (
"<CFRunLoopObserver 0x6000011b0be0 [0x10bee2b68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ffe6c009058>\n)}}",
"<CFRunLoopObserver 0x6000011b4640 [0x10bee2b68]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x115d51473), context = <CFRunLoopObserver context 0x600000bb5a40>}",
"<CFRunLoopObserver 0x6000011b0c80 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1161aedfc), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}",
"<CFRunLoopObserver 0x6000011b05a0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1117046ae), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x6000011b0b40 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1161aee75), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}",
"<CFRunLoopObserver 0x6000011b0aa0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ffe6c009058>\n)}}"
),
timers = (null),
currently 563029792 (7903325957143) / soft deadline in: 1.84467362e+10 sec (@ -1) / hard deadline in: 1.84467362e+10 sec (@ -1)
},

3 : <CFRunLoopMode 0x600001bb4340 [0x10bee2b68]>{name = GSEventReceiveRunLoopMode, port set = 0x2e03, queue = 0x600000eb5680, source = 0x600000eb5780 (not fired), timer port = 0x4e03,
sources0 = <CFBasicHash 0x600002e81ef0 [0x10bee2b68]>{type = mutable set, count = 1,
entries =>
0 : <CFRunLoopSource 0x6000015b5e00 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x110820188)}}
}
,
sources1 = <CFBasicHash 0x600002e81f20 [0x10bee2b68]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopSource 0x6000015b0f00 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x110820194)}}
}
,
observers = (null),
timers = (null),
currently 563029792 (7903327601583) / soft deadline in: 1.84467362e+10 sec (@ -1) / hard deadline in: 1.84467362e+10 sec (@ -1)
},

4 : <CFRunLoopMode 0x600001bb04e0 [0x10bee2b68]>{name = kCFRunLoopDefaultMode, port set = 0x210b, queue = 0x600000eb1500, source = 0x600000eb1600 (not fired), timer port = 0x1d07,
sources0 = <CFBasicHash 0x600002e81e90 [0x10bee2b68]>{type = mutable set, count = 4,
entries =>
0 : <CFRunLoopSource 0x6000015b5e00 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x110820188)}}
4 : <CFRunLoopSource 0x6000015b0540 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x6000015b5080, callout = __handleEventQueue (0x116270912)}}
5 : <CFRunLoopSource 0x6000015b1f80 [0x10bee2b68]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000004b1680, callout = FBSSerialQueueRunLoopSourceHandler (0x118c99f0b)}}
6 : <CFRunLoopSource 0x6000015b0480 [0x10bee2b68]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x600002e81830, callout = __handleHIDEventFetcherDrain (0x11627091e)}}
}
,
sources1 = <CFBasicHash 0x600002e81ec0 [0x10bee2b68]>{type = mutable set, count = 3,
entries =>
0 : <CFRunLoopSource 0x6000015b0e40 [0x10bee2b68]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 13583, subsystem = 0x116a53448, context = 0x0}}
1 : <CFRunLoopSource 0x6000015b4480 [0x10bee2b68]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42499, subsystem = 0x116a654b0, context = 0x6000020acb80}}
2 : <CFRunLoopSource 0x6000015b0cc0 [0x10bee2b68]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x110820194)}}
}
,
observers = (
"<CFRunLoopObserver 0x6000011b0be0 [0x10bee2b68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ffe6c009058>\n)}}",
"<CFRunLoopObserver 0x6000011b4640 [0x10bee2b68]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x115d51473), context = <CFRunLoopObserver context 0x600000bb5a40>}",
"<CFRunLoopObserver 0x6000011b0c80 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1161aedfc), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}",
"<CFRunLoopObserver 0x6000011b05a0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1117046ae), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x6000011b0b40 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1161aee75), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}",
"<CFRunLoopObserver 0x6000011b0aa0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ffe6c009058>\n)}}"
),
timers = <CFArray 0x6000004b7360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (
0 : <CFRunLoopTimer 0x6000015b5440 [0x10bee2b68]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 563029756 (-36.444778 @ 7866885955998), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x10ac9327d / 0x115810fb9) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/UIKitCore.framework/UIKitCore), context = <CFRunLoopTimer context 0x6000035f3a40>}
)},
currently 563029792 (7903327672130) / soft deadline in: 1.8446744e+10 sec (@ 7866885955998) / hard deadline in: 1.8446744e+10 sec (@ 7866885955998)
},

5 : <CFRunLoopMode 0x600001bb08f0 [0x10bee2b68]>{name = kCFRunLoopCommonModes, port set = 0x3e0f, queue = 0x600000ebc400, source = 0x600000ebc500 (not fired), timer port = 0xa803,
sources0 = (null),
sources1 = (null),
observers = (null),
timers = (null),
currently 563029792 (7903329641921) / soft deadline in: 1.84467362e+10 sec (@ -1) / hard deadline in: 1.84467362e+10 sec (@ -1)
},
}
}

以下是摘要,显示的是启动后系统在主线程的 Runloop 中自动添加的观察者:

1
2
3
4
5
6
7
8
observers = (
"<CFRunLoopObserver 0x6000011b0be0 [0x10bee2b68]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ffe6c009058>\n)}}",
"<CFRunLoopObserver 0x6000011b4640 [0x10bee2b68]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x115d51473), context = <CFRunLoopObserver context 0x600000bb5a40>}",
"<CFRunLoopObserver 0x6000011b0c80 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x1161aedfc), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}",
"<CFRunLoopObserver 0x6000011b05a0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1117046ae), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x6000011b0b40 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x1161aee75), context = <CFRunLoopObserver context 0x7ffe6b7018e0>}",
"<CFRunLoopObserver 0x6000011b0aa0 [0x10bee2b68]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x11617f1b1), context = <CFArray 0x600002efc360 [0x10bee2b68]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7ffe6c009058>\n)}}"
),

其中第1和第6个观察者,两者的activities分别是0x10xa0activities表示的是当前 runloop 所处的状态,下面是 CFRunloop.h 中定义的 activities 枚举值:

1
2
3
4
5
6
7
8
9
10
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};

activities = 0x1,对应的就是 kCFRunLoopEntry;activities = 0xa0,对应的就是 kCFRunLoopBeforeWaiting | kCFRunLoopExit。

解释一下,kCFRunLoopEntry表示 runloop 已启动;kCFRunLoopBeforeWaiting表示 runloop 没事做了即将休眠;kCFRunLoopExit表示 runloop 已结束;从第1和第6个观察者的 callout 描述可以看到,它们的回调都是_wrapRunLoopWithAutoreleasePoolHandler,关于这个回调,目前我尚未查到其源码,它具体怎么实现自动释放池的创建和销毁暂不能一探究竟。按照网上各大博主的说法:

  • 在进入runloop时(kCFRunLoopEntry),观察者回调中会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
  • 在runloop即将休眠时(kCFRunLoopBeforeWaiting),观察者回调中会调用 _objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
  • 在runloop退出时(kCFRunLoopExit),观察者回调中会调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个观察者的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

虽然_wrapRunLoopWithAutoreleasePoolHandler的源码我暂时不得而知,但_objc_autoreleasePoolPush()和_objc_autoreleasePoolPop()的源码是可追踪的,后面的5.2小节中会具体讲解~

小结:系统提供的自动释放池,其创建发生在两个时机:runloop启动时 和 runloop即将休眠时。

3.2.手动

绝大多数情况下,系统会帮我们自动创建和销毁自动释放池。但是,根据 开发文档 的描述,以下场景下我们需要自己创建自动释放池:

  • If you are writing a program that is not based on a UI framework, such as a command-line tool.

如果你的项目不是基于UI framework的,比如命令行工具,则你需要自己创建自动释放池管理对象的生命周期。

  • If you write a loop that creates many temporary objects.

You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.

就是说,当你的循环体内创建了大量的临时对象时,你需要在循环体内创建一个自动释放池,这样临时对象会被标记为 autorelease 并在下次循环之前销毁。 在循环体内使用自动释放池可以降低内存峰值。

#示例2:在一个模态弹出的界面的viewDidLoad方法中模拟一个循环体内大量创建临时变量的情况:

1
2
3
4
5
6
7
8
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *string = [NSString stringWithFormat:@"ADFAFDSFNKFNASFSKJFNSFSDAFAOSJDSAFASFJDSJFIOWJFIOKALNFJASFASLKFLSAJFSLAFK"];
}
}
}

这是不使用自动释放池,反复弹出界面时的内存峰值状况图:

不使用释放池

这是使用自动释放池,反复弹出界面时的内存峰值状况图:

使用释放池

通过对比,可以清楚的看到,使用自动释放池时,即使创建了大量的临时对象,对象都能及时释放,内存的峰值也几乎没变化~

  • If you spawn a secondary thread.

You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.

当你创建了一个子线程时,也需要在线程启动时主动创建一个自动释放池,否则会产生内存泄露。

#示例3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import "Person.h"
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
@autoreleasepool {
NSThread *thread = [[[NSThread alloc] initWithTarget:self selector:@selector(onHandleThread) object:nil] autorelease];
[thread start];
}
return YES;
}
- (void)onHandleThread
{
@autoreleasepool {
Person *person = [[[Person alloc] init] autorelease];
person.name = @"IOI";
NSLog(@"+++++子线程:%@",[NSThread currentThread]);
}
}
@end


#import "Person.h"

@implementation Person
-(void)dealloc{
NSLog(@"++++PERSON IS DEALLOCED~");
}
@end

注意,先将 Build Settings->Objective-C Automotic Reference Counting 选项设置为NO,禁用ARC。运行后,输出日志:

1
2
+++++子线程:<NSThread: 0x600002bf8180>{number = 3, name = (null)}
++++PERSON IS DEALLOCED~

如日志所示,MRC环境下,在子线程中创建对象之后,使用 autorelease pool 并将对象标记为 autorelease,任务执行完成之后对象能正常释放。你也可以试着将 @autorelease{ } 块和 autorelease 去掉,运行之后对象不会销毁。

需要注意的是,子线程中需要自己创建释放池的说法只针对MRC的环境。在ARC的环境下,我们在子线程中创建新的对象后,该对象是能自动释放的。可以将 Build Settings->Objective-C Automotic Reference Counting 选项设置为 YES,启用ARC,修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(onHandleThread) object:nil];
[thread start];
return YES;
}
- (void)onHandleThread
{
Person *person = [[Person alloc] init];
person.name = @"IOI";
NSLog(@"+++++子线程:%@",[NSThread currentThread]);
}

输出日志:

1
2
+++++子线程:<NSThread: 0x600003d3f040>{number = 3, name = (null)}
++++PERSON IS DEALLOCED~

可以看到,ARC环境下,在子线程中创建了 Person 的实例对象,并且没有主动在子线程中添加释放池,但 person 对象最后确实销毁了。

那么为啥 ARC 环境下子线程中不使用自动释放池,临时对象也能释放呢?StackOverflow 的 这篇帖子 中有一种解释:

The latest version of the runtime (646, which shipped with OS X 10.10 and iOS 8) does indeed add a pool if you perform an autorelease without a pool on the current thread. The previous version of the runtime (551.1, which came with OS X 10.9 and iOS 7), also did this.

从 OSX 10.9 和 iOS7 开始,如果我们在线程中没有使用 autorelease pool,那么苹果会自动帮我们创建一个 pool 来释放对象,避免了内存泄露。我瞄了一眼这份开发文档的更新日志,上次更新时间竟还停留在 2012-07-17!下面是最新的 NSObject.mm 中的源码:

1
2
3
4
5
6
7
8
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}

这是 autorelease 方法底层实现的源码,在子线程中不创建自动释放池,直接将对象标记为 autorelease 时,会调用 autoreleaseFast(),参数为当前 autorelease 对象。

1
2
3
4
5
6
7
8
9
10
11
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}

因为没有创建释放池,所以 autoreleaseFast() 内会直接进入最后一个判断语句中,接着调用 autoreleaseNoPage(),参数为 autorelease 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());

bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}

// We are pushing an object or a non-placeholder'd pool.

// Install the first page.创建page
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page); //设置为当前正在使用的 pool

// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}

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

进入函数后,因为没设置释放池,所以 haveEmptyPoolPlaceholder() 返回 false;又因为传进来的 obj 参数是 autorelease 对象而非 POOL_BOUNDARY(边界对象),所以会直接创建 page 并将其设置为当前正在使用的 pool;随后将 autorelease 对象加入自动释放池的栈顶;在当前线程结束后 pool 会出栈,其中的 autorelease 对象也会随之释放。

可见,ARC环境下苹果已经更改了实现,子线程中会帮我们创建自动释放池,只是文档未更新

4.pool的销毁

在 3.1 小节中讲过,自动释放池的销毁时机主要有两个:runloop即将进入休眠时 和 runloop退出时~

  • AppKit 与自动释放池

AppKit and UIKit frameworks process each event-loop iteration (such as a mouse down event or a tap) within an autorelease pool block. Therefore you typically do not have to create an autorelease pool block yourself, or even see the code that is used to create one.

在事件循环(event-loop)开始时,AppKit 和 UIKit 会在主线程中创建一个自动释放池,不需要我们手动创建。当事件循环结束时,自动释放池会执行 drain 并销毁。

  • 线程与自动释放池

每个线程(包括主线程)都维护着自己的自动释放池栈,新创建的 pool 会被 push 到栈顶,当 pool 销毁时它会 pop 出栈。当前线程中被标记为 autorelease 的对象会被加入到栈顶的 pool 内。当线程销毁时,与当前线程相关的 pool 也都会通过 drain 自动销毁。

5.结构与源码

1
2
3
4
5
6
7
8
9
10
11
12
13
/***********************************************************************
Autorelease pool implementation

A thread's autorelease pool is a stack of pointers.
Each pointer is either an object to release, or POOL_BOUNDARY which is
an autorelease pool boundary.
A pool token is a pointer to the POOL_BOUNDARY for that pool. When
the pool is popped, every object hotter than the sentinel is released.
The stack is divided into a doubly-linked list of pages. Pages are added
and deleted as necessary.
Thread-local storage points to the hot page, where newly autoreleased
objects are stored.
**********************************************************************/

这是 NSObject.mm 源码中对 autorelease pool 的描述。它明确指出:

  • autorelease pool 是一个保存着指针的栈,此栈由一个双向的 pages(AutoreleasePoolPage) 链表组成,链表会在需要时被添加和删除;
  • 栈内指针指向的对象有两种:一种是被标记为 autorelease 而待 release 的对象;另一种是表示 pool 边界的哨兵对象(POOL_BOUNDARY);
  • 当 pool 被销毁而出栈时,哨兵对象前面的所有 autorelease 对象都会收到 release 消息而释放。
5.1.栈与链表

这里我们先讲一下上面提到的链表。Autorelease pool 并没有单独的结构体,它由若干个AutoreleasePoolPage以双向链表的形式组合而成。NSObject.mm 中有关于 AutoreleasePoolPage 的定义,成员变量部分摘要如下:

1
2
3
4
5
6
7
8
class AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage * const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
}
  • thread,表示当前线程,每个线程中都有与之对应的 pool,线程启动时创建pool,线程销毁时 drain pool;
  • id *next,游标,指向栈顶最新 add 进来的 autorelease 对象的下一个位置。
  • parent,指向父 page;
  • child,指向子 page;

每个线程中都有一个自动释放池的栈,栈内可能有一个或多个AutoreleasePoolPage对象;当将对象标记为autorelease时,此对象会被 add 到栈顶的 page 内;如果此 page 已满,则会创建一个新的 page 作为 child,新 page 的next指针被初始化在栈底;标记为 autorelease 的对象会被 add 到这个新建的 page 内;文字不如图表,这里借用博主 ©sunnyxx 的一张图能更清晰的看到添加 autorelease 对象后 page 的结构:

image

5.2.pool的创建

上面对 autorelease pool 的栈结构在原理上有了一个简单的描述,那么在代码层上 pool 是如何被创建的呢?

#示例4:使用 @autoreleasepool{ } 来手动创建一个自动释放池

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int count = 10;
void (^ blk)(void) = ^(){
printf("varable in block:%d",count);
};
blk();
}
return 0;
}

使用Clang编译后得到的结果如下,这里只截取关键部分的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};

int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int count = 10;
void (* blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}

可以看到,我们声明的 count 和 block 变量及其调用,在编译后的结果中是被 {__AtAutoreleasePool…} 包裹着的。所以实际上,@autoreleasepool{ } 就是通过__AtAutoreleasePool来实现的,它是一个结构体,其定义中包含了:

  • 构造函数,负责pool的创建或入栈;
  • 析构函数,负责pool的出栈、销毁;
  • 一个通过析构函数创建的 atautoreleasepoolobj 对象的指针。

构造函数和析构函数中分别调用了objc_autoreleasePoolPush()objc_autoreleasePoolPop(),关于这两个函数,我们可以在 NSObject.mm 源码中追踪到其具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void *objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
void *_objc_autoreleasePoolPush(void)
{
return objc_autoreleasePoolPush();
}
void _objc_autoreleasePoolPop(void *ctxt)
{
objc_autoreleasePoolPop(ctxt);
}

这里注意,_objc_autoreleasePoolPush 和_objc_autoreleasePoolPop 正是在3.2小节的最后提到的那两个函数,也就是之前说的,主线程会在 Runloop启动即将休眠时通过回调函数中调用_objc_autoreleasePoolPush()创建 pool,在即将休眠退出loop时调用_objc_autoreleasePoolPop()释放 pool。这两个函数的内部也只是调用了 objc_autoreleasePoolPush 和 objc_autoreleasePoolPop(注:函数名的开头不带下划线)。

因为本小节是介绍 pool 入栈,所以先只讲解 objc_autoreleasePoolPush 函数。它跟 pool 的创建和入栈有什么关系呢?Clang文档 中有一段关于此函数的描述:

1
2
void *objc_autoreleasePoolPush(void);
Creates a new autorelease pool that is enclosed by the current pool, makes that the current pool, and returns an opaque “handle” to it.

就是说,此函数就是用来创建 autorelease pool 并将其设置为当前 pool,它的内部的调用了AutoreleasePoolPage::push()函数:

1
2
3
4
5
6
7
8
9
10
11
12
static inline void *push() 
{
id *dest;
if (DebugPoolAllocation) {//调试模式
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY);
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}

if 语句第一个条件是针对调试模式,调试模式下每次自动释放池都会调用 autoreleaseNewPage() 方法,因为是第一次调用所以没有page,就创建一个新的 page。

1
2
3
4
5
6
id *autoreleaseNewPage(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page) return autoreleaseFullPage(obj, page);
else return autoreleaseNoPage(obj);
}

这是调试模式下的实现,我们不用关心它,我们只需要看autoreleaseFast()函数,其参数为POOL_BOUNDARY(注意这个参数,后面讲 autorelease 对象入栈时,会有对比)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();//获取当前 pool 栈中正在使用的page
if (page && !page->full()) {//如果当前有page且page没有满,则autorelease对象入栈
return page->add(obj);
} else if (page) {//如果此page已经满了,则新建一个page并将此autorelease对象压入栈
return autoreleaseFullPage(obj, page);
} else {//如果没有page,则会创建一个空的占位池
return autoreleaseNoPage(obj);
}
}
static inline AutoreleasePoolPage *hotPage()
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);//通过线程局部存储(Thread Local Storage)获取当前正在使用的page
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}

autoreleaseFast() 函数中,hotPage()用来获取当前 pool 栈内正在使用的 page,首次调用push()时没有 page,所以会进入最后一个判断,调用 autoreleaseNoPage()函数来创建一个空的占位池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());

bool pushExtraBoundary = false;//是否要 push 边界对象
if (haveEmptyPoolPlaceholder()) {//如果有占位的 EMPTY_POOL_PLACEHOLDER,则设置需"pushExtraBoundary=TRUE"
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
objc_autoreleaseNoPool(obj);
return nil;
}
//如果不是调试模式,且对象是边界对象,则去设置空的占位池,也就是 EMPTY_POOL_PLACEHOLDER
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
return setEmptyPoolPlaceholder();
}

// We are pushing an object or a non-placeholder'd pool.

// Install the first page.创建第一个page
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);//设置最新的page

// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}

// Push the requested object or pool.
return page->add(obj);//添加一个对象
}

第一次进入此函数时,haveEmptyPoolPlaceholder()会返回 false,所以会进入最后一个判断,调用 setEmptyPoolPlaceholder():

1
2
3
4
5
6
static inline id* setEmptyPoolPlaceholder()
{
assert(tls_get_direct(key) == nil);
tls_set_direct(key, (void *)EMPTY_POOL_PLACEHOLDER);
return EMPTY_POOL_PLACEHOLDER;
}

setEmptyPoolPlaceholder() 会通过 TLS 返回一个 EMPTY_POOL_PLACEHOLDER,其定义如下:

1
2
3
4
5
// EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
// pushed and it has never contained any objects. This saves memory
// when the top level (i.e. libdispatch) pushes and pops pools but
// never uses them.
# define EMPTY_POOL_PLACEHOLDER ((id*)1)

EMPTY_POOL_PLACEHOLDER 是一个空的占位池,它存储在 TLS 中,不包含任何对象。

至此,执行 @autorelease{ },第一次调用 push() 函数,最终创建了一个空的占位池~

5.3.对象入栈

如果在 @autorelease block 内创建了对象,那么这些对象就会被自动标记为 autorelease,并加入到自动释放池中:

1
2
3
4
5
6
7
8
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}

从函数实现的源码中可以看到,对象被标记为 autorelease 后,会调用 autoreleaseFast()。上面在介绍 pool 的创建时,已经介绍过这个函数,当时 autoreleaseFast 的参数是一个边界对象(哨兵),这里的参数则是被标记为 autorelease 的对象本身:

1
2
3
4
5
6
7
8
9
10
11
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();//获取当前 pool 栈中正在使用的page
if (page && !page->full()) {//如果当前有page且page没有满,则autorelease对象入栈
return page->add(obj);
} else if (page) {//如果此page已经满了,则新建一个page并将此autorelease对象压入栈
return autoreleaseFullPage(obj, page);
} else {//如果没有page,则会创建一个新的page并插入边界对象
return autoreleaseNoPage(obj);
}
}
  • 进入函数体之后,会先调用 hotPage() 获取当前在用的page,
1
2
3
4
5
6
7
8
static inline AutoreleasePoolPage *hotPage() 
{
AutoreleasePoolPage *result = (AutoreleasePoolPage *)
tls_get_direct(key);
if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
if (result) result->fastcheck();
return result;
}

当前是第一次调用 autorelease,栈中还没有创建好的page,只在5.2 小节中执行 @autorelease 时创建了一个 EMPTY_POOL_PLACEHOLDER(空的占位池),所以 hotPage() 会返回 nil。因此 autoreleaseFast()函数会继续调用 autoreleaseNoPage()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
id *autoreleaseNoPage(id obj)
{
assert(!hotPage());

bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
...
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
...
return setEmptyPoolPlaceholder();
}

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

// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}

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

因为之前在创建空的占位池时 autoreleaseNoPage()内调用了 setEmptyPoolPlaceholder(),所以这次 haveEmptyPoolPlaceholder() 会返回 true,进入第一个条件语句,pushExtraBoundary 被置为 true,所以最后创建了一个新的page,插入了边界对象,autorelease 对象入栈。

将对象压入栈时,会调用 add() 函数,首先解除保护;随后将对象插入到 page 中,并重新设置 next 指针;最后设置保护,返回值为当前 autorelease 对象下一位的索引。源码如下:

1
2
3
4
5
6
7
8
9
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;//将对象压到栈顶,并重新定位栈顶
protect();
return ret;
}

至此,第一个 autorelease 对象完成入栈~

后面再有对象被标记为 autorelease 时,依然走 autoreleaseFast(),但这时 page 已经存在,如果 page 没满,则直接 page->add() 让对象入栈;如果page已满,则调用 autoreleaseFullPage()新建page,再让对象入栈;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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());
//判断page->child是否存在
assert(page->full() || DebugPoolAllocation);

do {
if (page->child) page = page->child;//存在page->child的话就将 page置为page->child
else page = new AutoreleasePoolPage(page);////否则就去创建新的page
} while (page->full());

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

因为是栈式结构,所以先入栈的对象会在栈底,后入栈的对象则依次往栈顶的方向添加,具体可以参考 5.1 小节中的示意图。

5.4.对象释放

当 Runloop 即将进入休眠 和 Runloop 退出时,如线程执行完任务销毁时,autorelease pool 会 drain 并销毁,销毁前向其中保存着的 autorelease 对象依次发送 release 消息,从而释放对象。那么自动释放池的底层是如何释放对象的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}

static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;

//如果是空的占位池
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}

page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
//不是边界对象的情况,如MRC环境中,在子线程中没使用@autorelease,而直接调用了autorelease,这时添加到pool的对象就不是 POOL_BOUNDARY
if (stop == page->begin() && !page->parent) {//栈底不是哨兵对象
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}

if (PrintPoolHiwat) printHiwat();

page->releaseUntil(stop);//从栈顶开始向栈中的对象发送release消息,直到遇到第一个边界对象

// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}

static AutoreleasePoolPage *pageForPointer(uintptr_t p)
{
AutoreleasePoolPage *result;
uintptr_t offset = p % SIZE;

assert(offset >= sizeof(AutoreleasePoolPage));

result = (AutoreleasePoolPage *)(p - offset);
result->fastcheck();

return result;
}

objc_autoreleasePoolPop()即 Runloop 退出,自动释放池出栈时调用的函数。函数内会判断当前 token 是 EMPTY_POOL_PLACEHOLDER 还是 POOL_BOUNDARY 或者 autorelease 对象。如果是空的占位池,则清空占位池;如果栈底不是边界对象,则直接报错;其他情况,直接调用 releaseUntil(),释放对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void releaseUntil(id *stop) 
{
// Not recursive: we don't want to blow out the stack
// if a thread accumulates a stupendous amount of garbage

while (this->next != stop) {
// Restart from hotPage() every time, in case -release
// autoreleased more objects
AutoreleasePoolPage *page = hotPage();

// fixme I think this `while` can be `if`, but I can't prove it
while (page->empty()) {
page = page->parent;
setHotPage(page);
}

page->unprotect();
id obj = *--page->next;
memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
page->protect();

if (obj != POOL_BOUNDARY) {
objc_release(obj);
}
}

setHotPage(this);

#if DEBUG
// we expect any children to be completely empty
for (AutoreleasePoolPage *page = child; page; page = page->child) {
assert(page->empty());
}
#endif
}

上面章节中讲到,第一次将对象标记为 autorelease 时,在对象入栈前会往栈内插入一个边界对象(哨兵),这些边界对象可以视为一个 pool 的开始。当 pool 需要释放对象时,会从栈顶开始,依次向栈底边界对象的方向清理掉这中间的所有 autorelease 对象,具体流程为:

  1. 根据传入的哨兵对象地址找到哨兵对象所处的 page;
  2. 向当前 page 中晚于哨兵对象插入的所有 autorelease 对象发送 -release 消息;
  3. 回头移动 next 指针到正确位置;
  4. 从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page。

以上,就是 MRC 中有关 retain,release,autorelease 和自动释放池相关的原理和具体的实现。

ARC

1.作用

ARC,自动引用计数是苹果在 iOS5 之后推出的新技术。使用ARC时,我们不再需要手动添加retainreleaseautorelease这样的内存管理代码,编译器会在编译代码时自动帮我们添加。比如系统会自动帮我们在主线程 Runloop 中创建一个自动释放池,ARC下自动释放池内的对象会被自动标记为 autorelease,等 Runloop 退出时线程结束,pool被销毁,autorelease 对象就会 release。

ARC 不仅减少了开发者的麻烦,还避免了因为疏忽而导致的内存泄露或者过度释放等问题,我们只需要专注于自己的业务。

ARC differs from tracing garbage collection in that there is no background process that deallocates the objects asynchronously at runtime.[3] Unlike garbage collection, ARC does not handle reference cycles automatically. This means that as long as there are “strong” references to an object, it will not be deallocated. Strong cross-references can accordingly create deadlocks and memory leaks. It is up to the developer to break cycles by using weak references.

2.ARC与GC

ARC != 垃圾回收机制

ARC 发生在编译阶段,它是LLVM 3.0 编译器中的新特性。ARC 环境中,编译器在代码编译时帮我们将对象标记为 autorelease、retain、release,我们无须再写这些内存管理的代码,只需要用strong或者weak表示你对对象的所有权,或者注意像循环强引用这种问题即可。

JAVA中的垃圾回收机制则是在运行时检查对象的依赖,如果没有指针指向某个对象,那么这个对象就是垃圾对象,到达一定量级后系统就会自动清除这些垃圾对象,或者由调用者主动调用GC清理~

作为对比来说,OC中的引用计数更靠近垃圾回收机制一些,即当对象的引用计数=0时,就会自动调用对象的dealloc函数进行销毁。

3.变量修饰符

ARC下的四个变量修饰符:

  • __strong

对应属性修饰符中的strong,强引用,表示指针指向并拥有某个对象(引用计数+1)。这是声明对象时默认的修饰符,如果想释放强引用的对象,则将指针置为nil即可。ARC下当没有任何一个强引用指向对象时,对象才会销毁。

  • __weak

对应属性修饰符中的weak,弱引用,表示指向但不拥有某个对象(引用计数不变)。__weak 不会影响对象的释放,即只要没有强引用指向对象,即使有N个弱引用指向此对象,那么对象还是会销毁。对象被释放时,__weak 指针会被自动置为nil,不会引发野指针问题。

  • __autoreleasing

相当于MRC中的 autorelease,属性不能使用此修饰符。使用示例:

1
2
3
4
//ARC
NSDictionary * __autoreleasing dic = [[NSDictionary alloc] init];
//MRC
NSDictionary *dic = [[[NSDictionary alloc] init] autorelease];
  • __unsafe_unretained

对应属性修饰符中的unsafe_unretained,相当于 MRC 中的assign,只是将指针指向某对象,不改变其引用计数,不影响其释放。之所以以unsafe开头,是因为当此对象被释放时,原指针仍会指向此对象所在的内存区域,再次调用此指针时会引发野指针问题,不安全。

Clang文档中有这样一段描述:

Methods in the alloc, copy, init, mutableCopy, and new families are implicitly marked attribute((ns_returns_retained)). This may be suppressed by explicitly marking the method attribute((ns_returns_not_retained)).

与MRC一样,以 allocinitcopymutableCopynew开头的方法返回的对象,会被隐式的标记为ns_returns_retained,其他情况下创建的对象则会被标记为 autorelease 并加入自动释放池中。

4.注意事项

  • 不能再手动向对象发送retain, release, autorelease、retainCount、dealloc消息;
  • 可以重写 dealloc 方法,但不能在其内部调用[super dealloc];
  • 不能使用 NSAutoreleasePool 对象,而是使用 @autoreleasepool{};
  • 属性修饰符 weak 相当于原来的 assign,strong 相当于原来的 retain;
  • 注意循环引用问题;

在整个 XCode 中 开关 ARC,可以通过 Build Settings -> Objective-C Automotic Reference Counting 选项来设置。设置单独的某个或某几个文件开关 ARC 时,可到 Build Phases -> Compile Sources 中双击对应的文件,添加-fobjc-arc-fno-objc-arc即可~


相关参考:

#©wiki-Automatic_Reference_Counting

#©Clang llvm-ARC

#©Apple-NSAutoreleasePool

#©Apple-Autorelease Pool Blocks

#©sunnyxx

#©ZCMUCZX-自动释放池

#©不忘初“辛”-ARC技术要点

#©CocoaChina论坛7楼-垃圾收集机制与ARC


内存管理:AutoreleasePool
https://davidlii.cn/2018/05/21/autoreleasepool.html
作者
Davidli
发布于
2018年5月21日
许可协议