[英]Memory leak (ARC)
在上一個問題的后續中,我得出的結論是確實存在內存泄漏。 總而言之,內存開始時為9.7MB,每運行10次動畫就增加0.1MB,或者看起來是這樣。 我測試了大約12MB。
使用Instruments,我運行了一個包含以下內容的測試:
這是我得到的:
因此內存確實會增加。 但是,檢查這些世代似乎對這些泄漏不承擔任何責任。 例如,檢查“統計信息”面板時,列出的類別似乎指示CF,CG,NS等,以及Malloc
和__NSMallocBlock__
。
我還檢查了調用樹,並跟蹤了具有最高內存消耗的分支。
同樣,大多數內存消耗似乎與CoreGraphics有關。 在分配列表中,我可以更清楚地看到這些Malloc是什么。 結論是相同的。
提供完整的源代碼是不切實際的,因為該應用程序已經達到了數千行。 因此,我將概述似乎很重要的內容:
- (void)animateViewDidAppear
{
NSArray * buttons = @[self.registrationButton, self.facebookButton, self.twitterButton, self.linkedInButton];
// [...] A bunch of GLfloat calculations here
__block HyAnimationCollection * collection = [[HyAnimationCollection alloc] init];
for (int it=0 ; it < [buttons count] ; ++it) {
UIButton * button = [buttons objectAtIndex:it];
// [...] More GLfloat stuff
// Ease out back
__block HyAnimation * easeOutBack = [[HyAnimation alloc] init];
HyAnimationUpdateFunction easeOutBackUpdate = ^(id frame, BOOL done) {
[button setFrame:[frame CGRectValue]];
};
[easeOutBack setDelay:it * kHyAuthenticationViewControllerAnimationDelayFactor];
[easeOutBack setDuration:kHyAuthenticationViewControllerAnimationDuration];
[easeOutBack setEasing:^(GLfloat t) { return [HyEasing easeOutBack:t]; }];
[easeOutBack addRectAnimation:CGRectMake(origin.x, from, size.width, size.height)
to:CGRectMake(origin.x, to, size.width, size.height)];
[easeOutBack addUpdateFunction:easeOutBackUpdate];
[collection addAnimation:easeOutBack];
}
[collection addLock:self.animationLock];
[collection start];
}
self.animationLock
是一種鎖定機制,因此動畫不會重疊,但是它幾乎是自包含的,我無法想象為什么會導致泄漏。 但是,這些塊確實被發送到HyAnimation
,后者又被添加到HyAnimationCollection
,這使我更加煩惱,這就是我一直關注的地方。 總而言之,也許這些閉包可能正在創建圓形保留,所以讓我們看一下。 HyAnimation
的addUpdateFunction
實際上非常簡單:
- (void)addUpdateFunction:(HyAnimationUpdateFunction)update
{
[self.updateFunctions addObject: update];
}
由於self.updateFunctions
是NSMutableArray
,它保留了對這些塊的強引用。 因此,如果不釋放HyAnimation
,則這些塊也不會釋放,這意味着創建它們的初始范圍也不會釋放。 但是,HyAnimation是在方法內部聲明的,所以到目前為止,我仍然沒有理由不發布它。
這就是為什么我認為應該是因為動畫本身,即HyAnimationCollection
的[collection start];
。 這是有趣的部分:
for (HyAnimation * anim in self.animations) {
[anim start];
}
到現在為止還挺好。 這是HyAnimation
的start
:
- (void)start
{
[NSTimer scheduledTimerWithTimeInterval:self.delay
target:self
selector:@selector(scheduleAnimationWithTimer:)
userInfo:nil
repeats:NO];
// Send an udate notification
if ([self shouldUpdateImmediatly]) {
[self animateAt:0.0f done:NO];
}
}
這幾乎會延遲運行,並委托scheduleAnimationWithTimer:
但是,此方法設置了一個重復的計時器,因此將一直持續到動畫結束為止(我希望不要再重復了)。
- (void)scheduleAnimationWithTimer:(NSTimer*)timer
{
NSTimer * scheduled = [NSTimer scheduledTimerWithTimeInterval:self.frameRate
target:self
selector:@selector(animateWithTimer:)
userInfo:nil
repeats:YES];
// Trigger immediatly
[self setInitialDate:[NSDate date]];
[scheduled fire];
}
現在設置animateWithTimer:
- (void)animateWithTimer:(NSTimer*)timer
{
NSTimeInterval gone = [[NSDate date] timeIntervalSinceDate:self.initialDate];
GLfloat t = gone / self.duration;
BOOL done = gone >= self.duration;
// Ease
if (self.easing) {
t = self.easing(t);
}
// Make sure the last position is exact. This does not mean that t does not go over 1.0f during the animation, just the end
if (done && t > 1.0f) {
t = 1.0f;
}
// Animate
[self animateAt:t done:done];
// Finish
if (done) {
// Stop the timer
[timer invalidate];
// Notify completion
[self broadcastCompletion];
}
}
最后是animateAt:done:
- (void)animateAt:(GLfloat)t done:(BOOL)done
{
for (HyAnimationFunction anim in self.animations) {
anim(t, done);
}
}
也就是說,這最后一個方法調用了我先前在animateViewDidAppear
定義的塊。
首先,我相信HyAnimationCollection
和HyAnimation
實例被困在塊內,並且HyAnimation
對這些塊有很強的引用。 你同意嗎? 我該如何解決? 我嘗試使用__block
聲明兩個變量,但是對於這件事似乎沒有任何作用。
無論如何,我在將此問題與Instrument的內存分析相關時也遇到了麻煩,這就是為什么這篇文章這么長的原因。
多謝您與我保持聯系,對您的長時間閱讀深表歉意。
更新:
看來我是對的。 在@Stephen Darlington關於我之前的問題的帖子之后,我覆蓋了HyAnimationCollection
的dealloc
方法。 與他的建議相反,我沒有設置斷點,而是編寫了NSLog
。 直到現在都沒有記錄任何東西。
- (void)dealloc
{
NSLog(@"dealloced");
}
我所做的是在HyAnimation
添加了另一個屬性shouldCleanUpOnCompletion
。 如果為true,則animateWithTimer:
在完成時調用此方法:
- (void)cleanUp
{
// Get rid of everything
self.animations = [[NSMutableArray alloc] init];
self.updateFunctions = [[NSMutableArray alloc] init];
self.completionFunctions = [[NSMutableArray alloc] init];
}
我立即在控制台上看到日志,因此肯定存在保留周期。 問題是,我該如何解決? __block
應該解決嗎?
更新2
我剛剛意識到這已經足夠:
- (void)cleanUp
{
// Get rid of everything
// self.animations = [[NSMutableArray alloc] init];
// self.updateFunctions = [[NSMutableArray alloc] init];
self.completionFunctions = [[NSMutableArray alloc] init];
}
畢竟,這意味着completionFunctions
是創建閉包的功能。 我當前使用的唯一位置是HyAnimationCollection
,更具體的是:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
__block HyAnimationCollection * me = self;
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
static unsigned int complete = 0;
// We are only interested in knowing when the animations complete, so we can release the locks
++complete;
if (complete == [me.animations count]) {
// Reset, so the animation can be run again
complete = 0;
@synchronized(me) {
// Release all locks
[me.locks setLocked:NO];
// Done
me.isRunning = NO;
}
}
}];
return YES;
}
}
也就是說,保留周期必須在這里,對嗎? 但是哪里? 可以是第一個@synchronized
塊嗎?
更新
用__weak
代替__block
似乎已經成功了。 即使沒有cleanUp
,對象也會釋放,但是不能正確釋放。 首先,它會釋放HyAnimationCollection
,然后HyLockCollection
最后全部HyAnimation
秒。 HyLock
不應該發布,因為我在屬性中始終強烈引用它。
讓我們再看一下HyAnimationCollection
的addAnimation:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Prevent a strong circular reference
__weak HyAnimationCollection * me = self;
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
static unsigned int complete = 0;
// We are only interested in knowing when the animations complete, so we can release the locks
++complete;
if (complete == [me.animations count]) {
// Reset, so the animation can be run again
complete = 0;
@synchronized(me) {
// Release all locks
[me.locks setLocked:NO];
// Done
me.isRunning = NO;
}
}
}];
return YES;
}
}
問題在於僅在動畫結束時才調用此閉包。 因為HyAnimationCollection
是第一個發布的,所以這意味着當所有動畫結束時, HyAnimationCollection
已經發布,如您所見,這導致它不釋放鎖。
現在我遇到了相反的問題=)編碼非常有趣<3
好吧,問題似乎是這樣的:
-addAnimation:
中的塊中強烈引用了self
,則會得到一個保留周期 -addAnimation:
中的代碼塊中對self
引用較弱,那么self
被過早釋放,而您的完成代碼塊將毫無意義。 您需要的是一種在運行時打破保留周期的方法:
// Version 1:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
static unsigned int complete = 0;
// We are only interested in knowing when the animations complete, so we can release the locks
++complete;
if (complete == [self.animations count]) {
// Reset, so the animation can be run again
complete = 0;
@synchronized(self) {
// Release all locks
[self.locks setLocked:NO];
// Break retain cycle!!
[self.animations removeAllObjects];
// IF it still doesn't work, put a breakpoint at THIS LINE, and
// tell me if this code here runs ever.
// Done
self.isRunning = NO;
}
}
}];
return YES;
}
}
self
指向數組 animation
animation
指向一個塊 self
當這些連接之一斷開時,整個周期就會中斷。 打破循環的最簡單方法是通過在數組上調用-removeAllObjects
破壞數組指針(1)。
順便說一句,您的代碼的另一個問題是HyAnimationCollection
的static
變量。 一旦同時運行多個HyAnimationCollection
對象,這將是一個問題。 我只是創建一個實例變量unsigned int _completeCount;
:
(您也可以創建一個綜合屬性,但在這種情況下,我更喜歡使用iVar:
@interface HyAnimationCollection .....
{
unsigned int _completeCount; //is automatically initialized to 0 by the objc-runtime.
}
...
)
並在實現文件中:
// Version 2:
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
@synchronized(self) {
// We are only interested in knowing when the animations complete, so we can release the locks
++_completeCount;
if (_completeCount == [self.animations count]) {
// Reset, so the animation can be run again
_completeCount = 0;
// Release all locks
[self.locks setLocked:NO];
// Break retain cycle!!
NSLog(@"Breaking retain cycle (HyAnimationCollection)");
[self.animations removeAllObjects];
// Done
self.isRunning = NO;
}
}
}];
return YES;
}
}
2 .:如果需要,內部@synchronized
塊應包裝在整個塊周圍。 只是將其放在解鎖周圍並不能提供任何線程安全性。 (要對其進行測試,您還可以在該塊的開頭放置assert([NSThread isMainThread]);
行:如果它沒有崩潰,則意味着您可以省略整個@synchronized(self)
內容。代碼然后變為:)外部@synchronized
塊可能應該保留。
// Version 3.
- (BOOL)addAnimation:(HyAnimation*)animation
{
@synchronized(self) {
if (self.isRunning) {
return NO;
}
[self.animations addObject:animation];
// Self-subscribe for updates so we know when the animations end
[animation addCompletionFunction:^(HyAnimation * anim) {
assert([NSThread isMainThread]);
// We are only interested in knowing when the animations complete, so we can release the locks
++_completeCount;
if (_completeCount == [self.animations count]) {
// Reset, so the animation can be run again
_completeCount = 0;
// Release all locks
[self.locks setLocked:NO];
// Break retain cycle!!
NSLog(@"Breaking retain cycle (HyAnimationCollection)");
[self.animations removeAllObjects];
// Done
self.isRunning = NO;
}
}];
return YES;
}
}
我的提示:首先嘗試版本3。 如果崩潰,請改用版本2。 (該方案應該是DEBUG,而不是在開發過程中發布,否則assert()
可能無法正常工作,以后您可能會遇到一些奇怪的問題。)希望它能起作用...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.