概述
首先,我來描述一下這個問題,NSHashTable
是OC中用於弱引用對象的NSMutableSet
類型,在項目使用中,我們發現調用其 allObjects
方法會造成強引用關係,導致對象不會釋放。具體情況如下:
我們監聽了主線程的 Runloop
並在 kCFRunLoopBeforeWaiting | kCFRunLoopExit
時觸發,由於我們需要統計 UITableViewCell
的信息,所以我們將該 Observer
的 order
設置爲了 INT_MAX
,以保證在系統註冊的 CA Transaction 回調之後
static CFRunLoopObserverRef observer;
if (observer) {
return;
}
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping
kCFRunLoopExit); // before exiting a runloop run
observer = CFRunLoopObserverCreateWithHandler(NULL, // allocator
activities, // activities
YES, // repeats
INT_MAX, // order after CA transaction commits
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[self impTrack];
});
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
在 impTrack
方法中我們會將別處收集的 UITableViewCell
進行處理
- (void)impTrack
{
//邏輯判斷 這裏省略。。。
[self.sourceTable.allObjects enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// do your logic
}];
}
這裏調用了 allObjects
,可以從源碼中看出這是一個 NSArray
類型
@property (readonly, copy) NSArray<ObjectType> *allObjects;
一般來說 allObjects
會持有 UITableViewCell
強引用,但由 autoreleasepool
自動釋放之後,就不存在強引用問題。
問題:是在UITableView
不滑動時,UITableViewCell
可以正常釋放,在UITableView
滑動之後,UITableViewCell
無法釋放。
demo 鏈接: https://pan.baidu.com/s/1QlL3v5lMu4XzXIhGAbI6Sg 密碼: uo2o
Runloop Observer的問題
static CFRunLoopObserverRef observer;
if (observer) {
return;
}
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFOptionFlags activities = (kCFRunLoopBeforeWaiting | // before the run loop starts sleeping
kCFRunLoopExit); // before exiting a runloop run
observer = CFRunLoopObserverCreateWithHandler(NULL, // allocator
activities, // activities
YES, // repeats
INT_MAX, // order after CA transaction commits
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[self impTrack];
});
CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
CFRelease(observer);
AutoreleasePool
App啓動後,蘋果在主線程 RunLoop 裏註冊了兩個 Observer,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler()
。
第一個 Observer
監視的事件是 Entry(即將進入Loop),其回調內會調用 _objc_autoreleasePoolPush()
創建自動釋放池。其 order 是-2147483647
,優先級最高,保證創建釋放池發生在其他所有回調之前。
第二個 Observer 監視了兩個事件: BeforeWaiting
(準備進入休眠) 時調用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
釋放舊的池並創建新池;Exit(即將退出Loop) 時調用 _objc_autoreleasePoolPop()
來釋放自動釋放池。這個 Observer
的 order 是 2147483647
,優先級最低,保證其釋放池子發生在其他所有回調之後。
在主線程執行的代碼,通常是寫在諸如事件回調
、Timer回調
內的。這些回調會被 RunLoop
創建好的 AutoreleasePool
環繞着,所以不會出現內存泄漏,開發者也不必顯示創建 Pool 了。
界面更新
蘋果註冊了一個 Observer
監聽 BeforeWaiting
(即將進入休眠) 和 Exit (即將退出Loop) 事件,回調去執行一個很長的函數:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
。這個函數裏會遍歷所有待處理的 UIView/CAlayer
以執行實際的繪製和調整,並更新 UI 界面。
這個函數內部的調用棧大概是這樣的:
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
QuartzCore:CA::Transaction::observer_callback:
CA::Transaction::commit();
CA::Context::commit_transaction();
CA::Layer::layout_and_display_if_needed();
CA::Layer::layout_if_needed();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA::Layer::display_if_needed();
[CALayer display];
[UIView drawRect];
所以我們需要設定 order 值大於這個回調的 order 值,取了 INT_MAX
這裏的問題在於,將 order
設置爲了 INT_MAX
,這與 autoreleasepool
的_wrapRunLoopWithAutoreleasePoolHandler
的優先級設置相同。
我們通過斷點打印出各個 Observer
對象
(lldb) po [NSRunLoop currentRunLoop]
observers = (
"<CFRunLoopObserver 0x6000031280a0 [0x7fff8062d610]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c84b28), context = <CFArray 0x600000e51200 [0x7fff8062d610]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fa220009038>\n)}}",
"<CFRunLoopObserver 0x60000312ca00 [0x7fff8062d610]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x7fff48810abe), context = <CFRunLoopObserver context 0x600002b28620>}",
"<CFRunLoopObserver 0x6000031281e0 [0x7fff8062d610]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x7fff48cb5ad3), context = <CFRunLoopObserver context 0x7fa21ea04080>}",
"<CFRunLoopObserver 0x600003130280 [0x7fff8062d610]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x7fff2b454f32), context = <CFRunLoopObserver context 0x0>}",
"<CFRunLoopObserver 0x600003128000 [0x7fff8062d610]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x7fff48cb5b3c), context = <CFRunLoopObserver context 0x7fa21ea04080>}",
"<CFRunLoopObserver 0x600003128140 [0x7fff8062d610]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff48c84b28), context = <CFArray 0x600000e51200 [0x7fff8062d610]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fa220009038>\n)}}",
"<CFRunLoopObserver 0x600003134c80 [0x7fff8062d610]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _runLoopObserverWithBlockContext (0x7fff23d9e440), context = <CFRunLoopObserver context 0x600000e49fb0>}"
)
/* 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
};
0xa0 = 10100000 = kCFRunLoopBeforeWaiting + kCFRunLoopExit
order = 2147483647
的優先級有兩個,一個是我們註冊的 _runLoopObserverWithBlockContext
,另一個就是系統 autoreleasepool
的_wrapRunLoopWithAutoreleasePoolHandler
問題分析
根據 Runloop源碼 中CFRunLoopAddObserver
對 order
的排序,最終我們的回調會在系統 autoreleasepool
的回調之後
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
CHECK_FOR_FORK();
CFRunLoopModeRef rlm;
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rlo) || (NULL != rlo->_runLoop && rlo->_runLoop != rl)) return;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
CFSetAddValue(rl->_commonModeItems, rlo);
if (NULL != set) {
CFTypeRef context[2] = {rl, rlo};
/* add new item to all common-modes */
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {
rlm = __CFRunLoopFindMode(rl, modeName, true);
if (NULL != rlm && NULL == rlm->_observers) {
rlm->_observers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
}
if (NULL != rlm && !CFArrayContainsValue(rlm->_observers, CFRangeMake(0, CFArrayGetCount(rlm->_observers)), rlo)) {
Boolean inserted = false;
for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) {
CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
if (obs->_order <= rlo->_order) {
//這裏決定當order相等時,自定義回調會在系統回調之後
CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo);
inserted = true;
break;
}
}
if (!inserted) {
CFArrayInsertValueAtIndex(rlm->_observers, 0, rlo);
}
rlm->_observerMask |= rlo->_activities;
__CFRunLoopObserverSchedule(rlo, rl, rlm);
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
}
那麼我們創建對象,在一個 Runloop
循環時,是無法釋放掉,但是可以在下一次 Runloop
循環時,釋放上次創建的對象。
這樣不斷循環,當某一時刻,allObjects
獲得對象數量爲0時,就不會再強引用,所以cell
能夠釋放。
kCFRunloopExit 主線程Runloop在不切換mode情況下並不會調用
- 那爲什麼滑動
UITableView
之後,cell 就無法釋放了?
滑動UITableView
之後,runloop會切換mode,由kCFRunLoopDefaultMode
切換爲UITrackingRunLoopMode
,根據源碼,切換mode實際是調用CFRunLoopRunSpecific
這個函數
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
使用 lldb
來斷點這個函數,當滑動 UITableView
時
* thread #6, name = 'com.apple.uikit.eventfetch-thread', stop reason = breakpoint 4.1
* frame #0: 0x00007fff23d9a7b0 CoreFoundation`CFRunLoopRunSpecific
frame #1: 0x00007fff25939c71 Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 211
frame #2: 0x00007fff25939ee0 Foundation`-[NSRunLoop(NSRunLoop) runUntilDate:] + 72
frame #3: 0x00007fff48d39bfb UIKitCore`-[UIEventFetcher threadMain] + 138
frame #4: 0x00007fff2594f9eb Foundation`__NSThread__start__ + 1047
frame #5: 0x00007fff51c0c109 libsystem_pthread.dylib`_pthread_start + 148
frame #6: 0x00007fff51c07b8b libsystem_pthread.dylib`thread_start + 15
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
* frame #0: 0x00007fff23d9a7b0 CoreFoundation`CFRunLoopRunSpecific
frame #1: 0x00007fff38ba6c1a GraphicsServices`GSEventRunModal + 139
frame #2: 0x00007fff48c8b9ec UIKitCore`UIApplicationMain + 1605
frame #3: 0x000000010506f8fa TestIsland`main(argc=1, argv=0x00007ffeeab8fd00) at main.m:18:12
frame #4: 0x00007fff51a231fd libdyld.dylib`start + 1
確實會調用該方法,進行切換 mode
,Runloop
切換時,會將現場的mode
下的Runloop
退出,然後開啓一個新的mode
的Runloop
循環
問題就在於切換時,由於我們方法在 Autoreleasepool
執行 pop push
之後,會有一次沒有加入自動釋放池,導致變量無法釋放。
這也就是爲什麼手動添加 @autoreleasepool{} 可以防止這個問題,因爲 @autoreleasepool{} 原理是在大括號開始添加 push 以及結尾添加 pop。
最終修改將 order 從 INT_MAX 修改爲 INT_MAX-1,以保證我們方法包括在自動釋放池 push pop 操作中。
擴展
我們查看 GNU 源碼中 NSConcreteHashTable
的 allObjects
作爲參考:
- (NSArray*) allObjects
{
NSHashEnumerator enumerator;
NSUInteger index;
NSArray *a;
GS_BEGINITEMBUF(objects, nodeCount, id);
enumerator = NSEnumerateHashTable(self);
index = 0;
while (index < nodeCount
&& (objects[index] = NSNextHashEnumeratorItem(&enumerator)) != nil)
{
index++;
}
NSEndHashTableEnumeration(&enumerator);
a = [[[NSArray alloc] initWithObjects: objects count: index] autorelease];
GS_ENDITEMBUF();
return a;
}
或許你也有這樣的疑問,按道理說,autorelease
方法執行時沒有page時,會創建page,插入哨兵對象 POOL_BOUNDARY
,那麼下次push就不會創建一個新的page,那麼對象不就可以正常釋放的?
由於@autoreleasepool{}
最終執行 objc_autoreleasePoolPush
以及 objc_autoreleasePoolPop
,這裏分析其具體做了啥。
來看下 objc_autoreleasePoolPush
最終調用 autoreleaseNoPage(POOL_BOUNDARY)
方法
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",
objc_thread_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.
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);
}
那麼就會走 setEmptyPoolPlaceholder
,下一次遇到對象創建,或者方法時,pushExtraBoundary
就會爲YES,添加 POOL_BOUNDARY
然後添加對應的 obj
即push操作並不會插入一個 POOL_BOUNDARY
,而是標記了一個 Placeholder
,當下次有對象加入自動釋放池時,再進行插入 POOL_BOUNDARY
,然後再插入對象 obj
意味着每次runloop執行kRunloopEntry
時,進行push操作,會將標紅部分創建的obj獨立到 POOL_BOUNDARY
區間之外,無法釋放。
在方法中打印自動釋放池信息
OBJC_EXTERN void
_objc_autoreleasePoolPrint(void);
然後在需要打印處調用 _objc_autoreleasePoolPrint();
日誌輸出如下(下面是hotpage):
objc[36361]: AUTORELEASE POOLS for thread 0x10f55cdc0
objc[36361]: 61 releases pending.
objc[36361]: [0x7fbcf880c000] ................ PAGE (hot) (cold)
objc[36361]: [0x7fbcf880c038] 0x600001cde9d0 __NSArrayM
objc[36361]: [0x7fbcf880c040] 0x7fbcf752dd90 TestCell
objc[36361]: [0x7fbcf880c048] 0x7fbcf7432d70 TestCell
objc[36361]: [0x7fbcf880c050] 0x7fbcf7538da0 TestCell
objc[36361]: [0x7fbcf880c058] 0x7fbcf753ee10 TestCell
objc[36361]: [0x7fbcf880c060] 0x7fbcf742bed0 TestCell
objc[36361]: [0x7fbcf880c068] 0x7fbcf753d840 TestCell
objc[36361]: [0x7fbcf880c070] 0x7fbcf75417a0 TestCell
objc[36361]: [0x7fbcf880c078] 0x7fbcf752ff70 TestCell
objc[36361]: [0x7fbcf880c080] 0x7fbcf74378f0 TestCell
objc[36361]: [0x7fbcf880c088] 0x7fbcf7435120 TestCell
objc[36361]: [0x7fbcf880c090] 0x7fbcf75324b0 TestCell
objc[36361]: [0x7fbcf880c098] 0x7fbcf753b320 TestCell
objc[36361]: [0x7fbcf880c0a0] 0x7fbcf7529f30 TestCell
objc[36361]: [0x7fbcf880c0a8] 0x7fbcf7536020 TestCell
objc[36361]: [0x7fbcf880c0b0] 0x7fbcf7533ab0 TestCell
objc[36361]: [0x7fbcf880c0b8] 0x7fbcf7439ed0 TestCell
objc[36361]: [0x7fbcf880c0c0] 0x7fbcf7619220 TestCell
objc[36361]: [0x7fbcf880c0c8] 0x7fbcf742e690 TestCell
objc[36361]: [0x7fbcf880c0d0] 0x7fbcf76145b0 TestCell
objc[36361]: [0x7fbcf880c0d8] 0x600001cdea30 __NSArrayM
objc[36361]: [0x7fbcf880c0e0] 0x7fbcf752dd90 TestCell
objc[36361]: [0x7fbcf880c0e8] 0x7fbcf7432d70 TestCell
objc[36361]: [0x7fbcf880c0f0] 0x7fbcf7538da0 TestCell
objc[36361]: [0x7fbcf880c0f8] 0x7fbcf753ee10 TestCell
objc[36361]: [0x7fbcf880c100] 0x7fbcf742bed0 TestCell
objc[36361]: [0x7fbcf880c108] 0x7fbcf753d840 TestCell
objc[36361]: [0x7fbcf880c110] 0x7fbcf75417a0 TestCell
objc[36361]: [0x7fbcf880c118] 0x7fbcf752ff70 TestCell
objc[36361]: [0x7fbcf880c120] 0x7fbcf74378f0 TestCell
objc[36361]: [0x7fbcf880c128] 0x7fbcf7435120 TestCell
objc[36361]: [0x7fbcf880c130] 0x7fbcf75324b0 TestCell
objc[36361]: [0x7fbcf880c138] 0x7fbcf753b320 TestCell
objc[36361]: [0x7fbcf880c140] 0x7fbcf7529f30 TestCell
objc[36361]: [0x7fbcf880c148] 0x7fbcf7536020 TestCell
objc[36361]: [0x7fbcf880c150] 0x7fbcf7533ab0 TestCell
objc[36361]: [0x7fbcf880c158] 0x7fbcf7439ed0 TestCell
objc[36361]: [0x7fbcf880c160] 0x7fbcf7619220 TestCell
objc[36361]: [0x7fbcf880c168] 0x7fbcf742e690 TestCell
objc[36361]: [0x7fbcf880c170] 0x7fbcf76145b0 TestCell
objc[36361]: [0x7fbcf880c178] ################ POOL 0x7fbcf880c178
objc[36361]: [0x7fbcf880c180] 0x600001cf0fc0 __NSArrayM
objc[36361]: [0x7fbcf880c188] 0x7fbcf752dd90 TestCell
objc[36361]: [0x7fbcf880c190] 0x7fbcf7432d70 TestCell
objc[36361]: [0x7fbcf880c198] 0x7fbcf7538da0 TestCell
objc[36361]: [0x7fbcf880c1a0] 0x7fbcf753ee10 TestCell
objc[36361]: [0x7fbcf880c1a8] 0x7fbcf742bed0 TestCell
objc[36361]: [0x7fbcf880c1b0] 0x7fbcf753d840 TestCell
objc[36361]: [0x7fbcf880c1b8] 0x7fbcf75417a0 TestCell
objc[36361]: [0x7fbcf880c1c0] 0x7fbcf752ff70 TestCell
objc[36361]: [0x7fbcf880c1c8] 0x7fbcf74378f0 TestCell
objc[36361]: [0x7fbcf880c1d0] 0x7fbcf7435120 TestCell
objc[36361]: [0x7fbcf880c1d8] 0x7fbcf75324b0 TestCell
objc[36361]: [0x7fbcf880c1e0] 0x7fbcf753b320 TestCell
objc[36361]: [0x7fbcf880c1e8] 0x7fbcf7529f30 TestCell
objc[36361]: [0x7fbcf880c1f0] 0x7fbcf7536020 TestCell
objc[36361]: [0x7fbcf880c1f8] 0x7fbcf7533ab0 TestCell
objc[36361]: [0x7fbcf880c200] 0x7fbcf7439ed0 TestCell
objc[36361]: [0x7fbcf880c208] 0x7fbcf7619220 TestCell
objc[36361]: [0x7fbcf880c210] 0x7fbcf742e690 TestCell
objc[36361]: [0x7fbcf880c218] 0x7fbcf76145b0 TestCell
objc[36361]: ##############
什麼鬼?不僅僅是__NSArrayM
,連cell都加入了自動釋放池。和預想的不一樣,我們模擬一下代碼,輸出日誌,以防其他地方影響:
OBJC_EXTERN void
_objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSHashTable *hashtb = [NSHashTable weakObjectsHashTable];
NSObject *objc = [[NSObject alloc] init];
NSObject *objc2 = [[NSObject alloc] init];
[hashtb addObject:objc];
[hashtb addObject:objc2];
@autoreleasepool {
hashtb.allObjects;
_objc_autoreleasePoolPrint();
}
}
return 0;
}
輸出日誌爲:
objc[36295]: ##############
objc[36295]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[36295]: 6 releases pending.
objc[36295]: [0x102017000] ................ PAGE (hot) (cold)
objc[36295]: [0x102017038] ################ POOL 0x102017038
objc[36295]: [0x102017040] 0x100717440 NSConcreteHashTable
objc[36295]: [0x102017048] ################ POOL 0x102017048
objc[36295]: [0x102017050] 0x10071b820 __NSArrayM
objc[36295]: [0x102017058] 0x10070dd30 NSObject
objc[36295]: [0x102017060] 0x10070df80 NSObject
objc[36295]: ##############
Program ended with exit code: 0
說明其allObject方法是會造成裏面元素加入自動釋放池。而且這份自動釋放池信息和之前的對比,可以明顯發現,少了一個 POOL_BOUNDARY
,導致裏面的元素無法正常釋放。
demo 鏈接: https://pan.baidu.com/s/1QlL3v5lMu4XzXIhGAbI6Sg 密碼: uo2o